소스 검색

Canvas2D: auto size feature + bugs fixing

nockawa 9 년 전
부모
커밋
7b6054135a

+ 22 - 27
src/Canvas2d/babylon.bounding2d.ts

@@ -28,46 +28,45 @@
             this.extent = Vector2.Zero();
         }
 
-        public static CreateFromSize(size: Size, origin?: Vector2): BoundingInfo2D {
+        public static CreateFromSize(size: Size): BoundingInfo2D {
             let r = new BoundingInfo2D();
-            BoundingInfo2D.CreateFromSizeToRef(size, r, origin);
+            BoundingInfo2D.CreateFromSizeToRef(size, r);
             return r;
         }
 
-        public static CreateFromRadius(radius: number, origin?: Vector2): BoundingInfo2D {
+        public static CreateFromRadius(radius: number): BoundingInfo2D {
             let r = new BoundingInfo2D();
-            BoundingInfo2D.CreateFromRadiusToRef(radius, r, origin);
+            BoundingInfo2D.CreateFromRadiusToRef(radius, r);
             return r;
         }
 
-        public static CreateFromPoints(points: Vector2[], origin?: Vector2): BoundingInfo2D {
+        public static CreateFromPoints(points: Vector2[]): BoundingInfo2D {
             let r = new BoundingInfo2D();
-            BoundingInfo2D.CreateFromPointsToRef(points, r, origin);
+            BoundingInfo2D.CreateFromPointsToRef(points, r);
 
             return r;
         }
 
-        public static CreateFromSizeToRef(size: Size, b: BoundingInfo2D, origin?: Vector2) {
-            b.center = new Vector2(size.width / 2, size.height / 2);
-            b.extent = b.center.clone();
-            if (origin) {
-                b.center.x -= size.width * origin.x;
-                b.center.y -= size.height * origin.y;
+        public static CreateFromSizeToRef(size: Size, b: BoundingInfo2D) {
+            if (!size) {
+                size = Size.Zero();
             }
+            b.center.x = +size.width / 2;
+            b.center.y = +size.height / 2;
+            b.extent.x = b.center.x;
+            b.extent.y = b.center.y;
             b.radius = b.extent.length();
         }
 
-        public static CreateFromRadiusToRef(radius: number, b: BoundingInfo2D, origin?: Vector2) {
-            b.center = Vector2.Zero();
-            if (origin) {
-                b.center.x -= radius * origin.x;
-                b.center.y -= radius * origin.y;
-            }
-            b.extent = new Vector2(radius, radius);
-            b.radius = radius;
+        public static CreateFromRadiusToRef(radius: number, b: BoundingInfo2D) {
+            b.center.x = b.center.y = 0;
+            let r = +radius;
+            b.extent.x = r;
+            b.extent.y = r;
+            b.radius = r;
         }
 
-        public static CreateFromPointsToRef(points: Vector2[], b: BoundingInfo2D, origin?: Vector2) {
+        public static CreateFromPointsToRef(points: Vector2[], b: BoundingInfo2D) {
             let xmin = Number.MAX_VALUE, ymin = Number.MAX_VALUE, xmax = Number.MIN_VALUE, ymax = Number.MIN_VALUE;
             for (let p of points) {
                 xmin = Math.min(p.x, xmin);
@@ -75,17 +74,13 @@
                 ymin = Math.min(p.y, ymin);
                 ymax = Math.max(p.y, ymax);
             }
-            BoundingInfo2D.CreateFromMinMaxToRef(xmin, xmax, ymin, ymax, b, origin);
+            BoundingInfo2D.CreateFromMinMaxToRef(xmin, xmax, ymin, ymax, b);
         }
 
-        public static CreateFromMinMaxToRef(xmin: number, xmax: number, ymin: number, ymax: number, b: BoundingInfo2D, origin?: Vector2) {
+        public static CreateFromMinMaxToRef(xmin: number, xmax: number, ymin: number, ymax: number, b: BoundingInfo2D) {
             let w = xmax - xmin;
             let h = ymax - ymin;
             b.center = new Vector2(xmin + w / 2, ymin + h / 2);
-            if (origin) {
-                b.center.x -= w * origin.x;
-                b.center.y -= h * origin.y;
-            }
             b.extent = new Vector2(xmax - b.center.x, ymax - b.center.y);
             b.radius = b.extent.length();
         }

+ 1 - 1
src/Canvas2d/babylon.canvas2d.ts

@@ -1127,7 +1127,7 @@
             marginLeft               ?: number | string,
             marginRight              ?: number | string,
             marginBottom             ?: number | string,
-            margin                   ?: string,
+            margin                   ?: number | string,
             marginHAlignment         ?: number,
             marginVAlignment         ?: number,
             marginAlignment          ?: string,

+ 106 - 6
src/Canvas2d/babylon.canvas2dLayoutEngine.ts

@@ -6,13 +6,33 @@
      * Note that for performance reason, each different Layout Engine type must be instanced only once, good practice is through a static `Singleton`property defined in the type itself.
      * If data has to be associated to a given primitive you can use the SmartPropertyPrim.addExternalData API to do it.
      */
-    export class LayoutEngineBase {
+    export class LayoutEngineBase implements ILockable {
+        constructor() {
+            this.layoutDirtyOnPropertyChangedMask = 0;
+        }
+
         public updateLayout(prim: Prim2DBase) {
         }
 
         public get isChildPositionAllowed(): boolean {
             return false;
         }
+
+        isLocked(): boolean {
+            return this._isLocked;
+        }
+
+        lock(): boolean {
+            if (this._isLocked) {
+                return false;
+            }
+            this._isLocked = true;
+            return true;
+        }
+
+        public layoutDirtyOnPropertyChangedMask;
+
+        private _isLocked: boolean;
     }
 
     @className("CanvasLayoutEngine")
@@ -25,10 +45,8 @@
         public updateLayout(prim: Prim2DBase) {
 
             // If this prim is layoutDiry we update  its layoutArea and also the one of its direct children
-            // Then we recurse on each child where their respective layoutDirty will also be test, and so on.
             if (prim._isFlagSet(SmartPropertyPrim.flagLayoutDirty)) {
 
-                // Recurse
                 for (let child of prim.children) {
                     this._doUpdate(child);
                 }
@@ -40,17 +58,17 @@
         private _doUpdate(prim: Prim2DBase) {
             // Canvas ?
             if (prim instanceof Canvas2D) {
-                prim._layoutArea = prim.actualSize;
+                prim.layoutArea = prim.actualSize;
             }
 
             // Direct child of Canvas ?
             else if (prim.parent instanceof Canvas2D) {
-                prim._layoutArea = prim.owner.actualSize;
+                prim.layoutArea = prim.owner.actualSize;
             }
 
             // Indirect child of Canvas
             else {
-                prim._layoutArea = prim.parent.contentArea;
+                prim.layoutArea = prim.parent.contentArea;
             }
         }
 
@@ -59,4 +77,86 @@
         }
     }
 
+
+    @className("StackPanelLayoutEngine")
+    export class StackPanelLayoutEngine extends LayoutEngineBase {
+        constructor() {
+            super();
+            this.layoutDirtyOnPropertyChangedMask = Prim2DBase.sizeProperty.flagId;
+        }
+
+        public static get Horizontal(): StackPanelLayoutEngine {
+            if (!StackPanelLayoutEngine._horizontal) {
+                StackPanelLayoutEngine._horizontal = new StackPanelLayoutEngine();
+                StackPanelLayoutEngine._horizontal.isHorizontal = true;
+                StackPanelLayoutEngine._horizontal.lock();
+            }
+
+            return StackPanelLayoutEngine._horizontal;
+        }
+
+        public static get Vertical(): StackPanelLayoutEngine {
+            if (!StackPanelLayoutEngine._vertical) {
+                StackPanelLayoutEngine._vertical = new StackPanelLayoutEngine();
+                StackPanelLayoutEngine._vertical.isHorizontal = false;
+                StackPanelLayoutEngine._vertical.lock();
+            }
+
+            return StackPanelLayoutEngine._vertical;
+        }
+        private static _horizontal: StackPanelLayoutEngine = null;
+        private static _vertical: StackPanelLayoutEngine = null;
+
+
+        get isHorizontal(): boolean {
+            return this._isHorizontal;
+        }
+
+        set isHorizontal(val: boolean) {
+            if (this.isLocked()) {
+                return;
+            }
+            this._isHorizontal = val;
+        }
+
+        private _isHorizontal: boolean = true;
+
+        public updateLayout(prim: Prim2DBase) {
+            if (prim._isFlagSet(SmartPropertyPrim.flagLayoutDirty)) {
+
+                let x = 0;
+                let y = 0;
+                let h = this.isHorizontal;
+                let max = 0;
+
+                for (let child of prim.children) {
+                    let layoutArea = child.layoutArea;
+                    child.margin.computeArea(child.actualSize, layoutArea);
+
+                    max = Math.max(max, h ? layoutArea.height : layoutArea.width);
+
+                }
+
+                for (let child of prim.children) {
+                    child.layoutAreaPos = new Vector2(x, y);
+
+                    let layoutArea = child.layoutArea;
+
+                    if (h) {
+                        x += layoutArea.width;
+                        child.layoutArea = new Size(layoutArea.width, max);
+                    } else {
+                        y += layoutArea.height;
+                        child.layoutArea = new Size(max, layoutArea.height);
+                    }
+                }
+                prim._clearFlags(SmartPropertyPrim.flagLayoutDirty);
+            }
+
+        }
+
+        get isChildPositionAllowed(): boolean {
+            return false;
+        }
+    }
 }

+ 11 - 6
src/Canvas2d/babylon.ellipse2d.ts

@@ -174,7 +174,7 @@
             if (this._actualSize) {
                 return this._actualSize;
             }
-            return this._size;
+            return this.size;
         }
 
         public set actualSize(value: Size) {
@@ -199,7 +199,7 @@
         }
 
         protected updateLevelBoundingInfo() {
-            BoundingInfo2D.CreateFromSizeToRef(this.size, this._levelBoundingInfo);
+            BoundingInfo2D.CreateFromSizeToRef(this.actualSize, this._levelBoundingInfo);
         }
 
         /**
@@ -240,7 +240,7 @@
             marginLeft        ?: number | string,
             marginRight       ?: number | string,
             marginBottom      ?: number | string,
-            margin            ?: string,
+            margin            ?: number | string,
             marginHAlignment  ?: number,
             marginVAlignment  ?: number,
             marginAlignment   ?: string,
@@ -261,10 +261,15 @@
 
             super(settings);
 
-            let size = settings.size || (new Size(settings.width || 10, settings.height || 10));
-            let sub  = (settings.subdivisions == null) ? 64 : settings.subdivisions;
+            if (settings.size != null) {
+                this.size = settings.size;
+            }
+            else if (settings.width || settings.height) {
+                let size = new Size(settings.width, settings.height);
+                this.size = size;
+            }
 
-            this.size         = size;
+            let sub  = (settings.subdivisions == null) ? 64 : settings.subdivisions;
             this.subdivisions = sub;
         }
 

+ 2 - 1
src/Canvas2d/babylon.group2d.ts

@@ -49,12 +49,13 @@
             width             ?: number,
             height            ?: number,
             cacheBehavior     ?: number,
+            layoutEngine      ?: LayoutEngineBase | string,
             isVisible         ?: boolean,
             marginTop         ?: number | string,
             marginLeft        ?: number | string,
             marginRight       ?: number | string,
             marginBottom      ?: number | string,
-            margin            ?: string,
+            margin            ?: number | string,
             marginHAlignment  ?: number,
             marginVAlignment  ?: number,
             marginAlignment   ?: string,

+ 4 - 2
src/Canvas2d/babylon.lines2d.ts

@@ -191,7 +191,7 @@
 
         public set points(value: Vector2[]) {
             this._points = value;
-            this._setFlags(SmartPropertyPrim.flagLevelBoundingInfoDirty);
+            this._boundingBoxDirty();
         }
 
         @modelLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 2, pi => Lines2D.fillThicknessProperty = pi)
@@ -371,7 +371,7 @@
             marginLeft       ?: number | string,
             marginRight      ?: number | string,
             marginBottom     ?: number | string,
-            margin           ?: string,
+            margin           ?: number | string,
             marginHAlignment ?: number,
             marginVAlignment ?: number,
             marginAlignment  ?: string,
@@ -395,6 +395,8 @@
             this._borderVB = null;
             this._borderIB = null;
 
+            this._size = Size.Zero();
+
             this._boundingMin = null;
             this._boundingMax = null;
 

+ 168 - 70
src/Canvas2d/babylon.prim2dBase.ts

@@ -429,6 +429,10 @@
             this._setType(1, PrimitiveThickness.Auto);
             this._setType(2, PrimitiveThickness.Auto);
             this._setType(3, PrimitiveThickness.Auto);
+            this._pixels[0] = 0;
+            this._pixels[1] = 0;
+            this._pixels[2] = 0;
+            this._pixels[3] = 0;
         }
 
         public fromString(margin: string) {
@@ -455,7 +459,7 @@
 
         }
 
-        public fromStrings(owner: Prim2DBase, top: string, left: string, right: string, bottom: string): PrimitiveThickness {
+        public fromStrings(top: string, left: string, right: string, bottom: string): PrimitiveThickness {
             this._clear();
 
             this._setStringValue(top, 0, false);
@@ -466,7 +470,7 @@
             return this;
         }
 
-        public fromPixels(owner: Prim2DBase, top: number, left: number, right: number, bottom: number): PrimitiveThickness {
+        public fromPixels(top: number, left: number, right: number, bottom: number): PrimitiveThickness {
             this._clear();
 
             this._pixels[0] = top;
@@ -477,20 +481,35 @@
             return this;
         }
 
+        public fromUniformPixels(margin: number): PrimitiveThickness {
+            this._clear();
+
+            this._pixels[0] = margin;
+            this._pixels[1] = margin;
+            this._pixels[2] = margin;
+            this._pixels[3] = margin;
+            this._changedCallback();
+            return this;
+        }
+
         public auto(): PrimitiveThickness {
             this._clear();
 
             this._flags = (PrimitiveThickness.Auto << 0) | (PrimitiveThickness.Auto << 4) | (PrimitiveThickness.Auto << 8) | (PrimitiveThickness.Auto << 12);
+            this._pixels[0] = 0;
+            this._pixels[1] = 0;
+            this._pixels[2] = 0;
+            this._pixels[3] = 0;
             this._changedCallback();
             return this;
         }
 
         private _clear() {
             this._flags = 0;
-            this._pixels[0] = null;
-            this._pixels[1] = null;
-            this._pixels[2] = null;
-            this._pixels[3] = null;
+            this._pixels[0] = 0;
+            this._pixels[1] = 0;
+            this._pixels[2] = 0;
+            this._pixels[3] = 0;
             this._percentages[0] = null;
             this._percentages[1] = null;
             this._percentages[2] = null;
@@ -531,7 +550,7 @@
                     return true;
                 }
                 this._setType(index, PrimitiveThickness.Auto);
-                this._pixels[index] = null;
+                this._pixels[index] = 0;
                 if (emitChanged) {
                     this._changedCallback();
                 }
@@ -835,8 +854,15 @@
         public static Percentage = 0x4;
         public static Pixel      = 0x8;
 
-        private _computePixels(index: number, type: number, sourceArea: Size, emitChanged: boolean) {
-            if (this._getType(index, false) !== PrimitiveThickness.Percentage) {
+        private _computePixels(index: number, sourceArea: Size, emitChanged: boolean) {
+            let type = this._getType(index, false);
+
+            if (type === PrimitiveThickness.Inherit) {
+                this._parentAccess()._computePixels(index, sourceArea, emitChanged);
+                return;
+            }
+
+            if (type !== PrimitiveThickness.Percentage) {
                 return;
             }
 
@@ -869,7 +895,7 @@
                     if (isLeftAuto) {
                         dstOffset.x = 0;
                     } else {
-                        this._computePixels(1, leftType, sourceArea, true);
+                        this._computePixels(1, sourceArea, true);
                         dstOffset.x = this.leftPixels;
                     }
                     dstArea.width = width;
@@ -881,7 +907,7 @@
                     if (isRightAuto) {
                         dstOffset.x = Math.round(sourceArea.width - width);
                     } else {
-                        this._computePixels(2, rightType, sourceArea, true);
+                        this._computePixels(2, sourceArea, true);
                         dstOffset.x = Math.round(sourceArea.width - (width + this.rightPixels));
                     }
                     dstArea.width = width;
@@ -892,13 +918,13 @@
                     if (isLeftAuto) {
                         dstOffset.x = 0;
                     } else {
-                        this._computePixels(1, leftType, sourceArea, true);
+                        this._computePixels(1, sourceArea, true);
                         dstOffset.x = this.leftPixels;
                     }
 
                     let right = 0;
                     if (!isRightAuto) {
-                        this._computePixels(2, rightType, sourceArea, true);
+                        this._computePixels(2, sourceArea, true);
                         right = this.rightPixels;
                     }
                     dstArea.width = sourceArea.width - (dstOffset.x + right);
@@ -907,10 +933,10 @@
                 case PrimitiveAlignment.AlignCenter:
                 {
                     if (!isLeftAuto) {
-                        this._computePixels(1, leftType, sourceArea, true);
+                        this._computePixels(1, sourceArea, true);
                     }
                     if (!isRightAuto) {
-                        this._computePixels(2, rightType, sourceArea, true);
+                        this._computePixels(2, sourceArea, true);
                     }
 
                     let offset = (isLeftAuto ? 0 : this.leftPixels) - (isRightAuto ? 0 : this.rightPixels);
@@ -926,7 +952,7 @@
                     if (isTopAuto) {
                         dstOffset.y = sourceArea.height - height;
                     } else {
-                        this._computePixels(0, topType, sourceArea, true);
+                        this._computePixels(0, sourceArea, true);
                         dstOffset.y = Math.round(sourceArea.height - (height + this.topPixels));
                     }
                     dstArea.height = height;
@@ -938,7 +964,7 @@
                     if (isBottomAuto) {
                         dstOffset.y = 0;
                     } else {
-                        this._computePixels(3, bottomType, sourceArea, true);
+                        this._computePixels(3, sourceArea, true);
                         dstOffset.y = this.bottomPixels;
                     }
                     dstArea.height = height;
@@ -950,13 +976,13 @@
                     if (isBottomAuto) {
                         dstOffset.y = 0;
                     } else {
-                        this._computePixels(3, bottomType, sourceArea, true);
+                        this._computePixels(3, sourceArea, true);
                         dstOffset.y = this.bottomPixels;
                     }
 
                     let top = 0;
                     if (!isTopAuto) {
-                        this._computePixels(0, topType, sourceArea, true);
+                        this._computePixels(0, sourceArea, true);
                         top = this.topPixels;
                     }
                     dstArea.height = sourceArea.height - (dstOffset.y + top);
@@ -965,10 +991,10 @@
                 case PrimitiveAlignment.AlignCenter:
                 {
                     if (!isTopAuto) {
-                        this._computePixels(0, topType, sourceArea, true);
+                        this._computePixels(0, sourceArea, true);
                     }
                     if (!isBottomAuto) {
-                        this._computePixels(3, bottomType, sourceArea, true);
+                        this._computePixels(3, sourceArea, true);
                     }
 
                     let offset = (isBottomAuto ? 0 : this.bottomPixels) - (isTopAuto ? 0 : this.topPixels);
@@ -980,34 +1006,26 @@
         }
 
         public compute(sourceArea: Size, dstOffset: Vector2, dstArea: Size) {
-            // Fetch some data
-            let topType = this._getType(0, true);
-            let leftType = this._getType(1, true);
-            let rightType = this._getType(2, true);
-            let bottomType = this._getType(3, true);
-            let isTopAuto = topType === PrimitiveThickness.Auto;
-            let isLeftAuto = leftType === PrimitiveThickness.Auto;
-            let isRightAuto = rightType === PrimitiveThickness.Auto;
-            let isBottomAuto = bottomType === PrimitiveThickness.Auto;
+            this._computePixels(0, sourceArea, true);
+            this._computePixels(1, sourceArea, true);
+            this._computePixels(2, sourceArea, true);
+            this._computePixels(3, sourceArea, true);
 
-            if (!isTopAuto) {
-                this._computePixels(0, topType, sourceArea, true);
-            }
-            if (!isLeftAuto) {
-                this._computePixels(1, leftType, sourceArea, true);
-            }
-            if (!isRightAuto) {
-                this._computePixels(2, rightType, sourceArea, true);
-            }
-            if (!isBottomAuto) {
-                this._computePixels(3, bottomType, sourceArea, true);
-            }
+            dstOffset.x = this.leftPixels;
+            dstArea.width = sourceArea.width - (dstOffset.x + this.rightPixels);
+
+            dstOffset.y = this.bottomPixels;
+            dstArea.height = sourceArea.height - (dstOffset.y + this.topPixels);
+        }
 
-            dstOffset.x = isLeftAuto ? 0 : this.leftPixels;
-            dstArea.width = sourceArea.width - (dstOffset.x + (isRightAuto ? 0 : this.rightPixels));
+        computeArea(sourceArea: Size, result: Size) {
+            this._computePixels(0, sourceArea, true);
+            this._computePixels(1, sourceArea, true);
+            this._computePixels(2, sourceArea, true);
+            this._computePixels(3, sourceArea, true);
 
-            dstOffset.y = isTopAuto ? 0 : this.bottomPixels;
-            dstArea.height = sourceArea.height - (dstOffset.y + (isTopAuto ? 0 : this.topPixels));
+            result.width  = this.leftPixels + sourceArea.width + this.rightPixels;
+            result.height = this.bottomPixels + sourceArea.height + this.topPixels;
         }
     }
 
@@ -1095,12 +1113,13 @@
             rotation            ?: number,
             scale               ?: number,
             origin              ?: Vector2,
+            layoutEngine        ?: LayoutEngineBase | string,
             isVisible           ?: boolean,
             marginTop           ?: number | string,
             marginLeft          ?: number | string,
             marginRight         ?: number | string,
             marginBottom        ?: number | string,
-            margin              ?: string,
+            margin              ?: number | string,
             marginHAlignment    ?: number,
             marginVAlignment    ?: number,
             marginAlignment     ?: string,
@@ -1142,9 +1161,12 @@
 
             // Fields initialization
             this._layoutEngine               = CanvasLayoutEngine.Singleton;
-            this._size                       = Size.Zero();
+            this._size                       = null; //Size.Zero();
+            this._actualSize                 = null;
+            this._boundingSize               = null;
             this._layoutArea                 = Size.Zero();
-            this._paddingOffset              = Vector2.Zero();
+            this._layoutAreaPos              = Vector2.Zero();
+            this._marginOffset               = Vector2.Zero();
             this._parentMargingOffset        = Vector2.Zero();
             this._parentContentArea          = Size.Zero();
             this._contentArea                = new Size(null, null);
@@ -1204,6 +1226,22 @@
             this.levelVisible = (settings.isVisible==null) ? true : settings.isVisible;
             this.origin = settings.origin || new Vector2(0.5, 0.5);
 
+            // Layout Engine
+            if (settings.layoutEngine != null) {
+                if (typeof settings.layoutEngine === "string") {
+                    let name = (<string>settings.layoutEngine).toLocaleLowerCase().trim();
+                    if (name === "canvas" || name === "canvaslayoutengine") {
+                        this.layoutEngine = CanvasLayoutEngine.Singleton;
+                    } else if (name.indexOf("stackpanel") === 0 || name.indexOf("horizontalstackpanel") === 0) {
+                        this.layoutEngine = StackPanelLayoutEngine.Horizontal;
+                    } else if (name.indexOf("verticalstackpanel")===0) {
+                        this.layoutEngine = StackPanelLayoutEngine.Vertical;
+                    }
+                } else if (settings.layoutEngine instanceof LayoutEngineBase) {
+                    this.layoutEngine = <LayoutEngineBase>settings.layoutEngine;
+                }
+            }
+
             // Set the layout/margin stuffs
             if (settings.marginTop) {
                 this.margin.setTop(settings.marginTop);
@@ -1219,7 +1257,11 @@
             }
 
             if (settings.margin) {
-                this.margin.fromString(settings.margin);
+                if (typeof settings.margin === "string") {
+                    this.margin.fromString(<string>settings.margin);
+                } else {
+                    this.margin.fromUniformPixels(<number>settings.margin);
+                }
             }
 
             if (settings.marginHAlignment) {
@@ -1476,6 +1518,9 @@
             this.markAsDirty("position");
         }
 
+        private static boundinbBoxReentrency = false;
+        private static nullSize = Size.Zero();
+
         /**
          * Size of the primitive or its bounding area
          * BEWARE: if you change only size.width or height it won't trigger a property change and you won't have the expected behavior.
@@ -1483,6 +1528,25 @@
          */
         @dynamicLevelProperty(2, pi => Prim2DBase.sizeProperty = pi, false, true)
         public get size(): Size {
+
+            if (!this._size || this._size.width==null || this._size.height==null) {
+
+                if (Prim2DBase.boundinbBoxReentrency) {
+                    return Prim2DBase.nullSize;
+                }
+
+                if (this._boundingSize) {
+                    return this._boundingSize;
+                }
+
+                Prim2DBase.boundinbBoxReentrency = true;
+                let b = this.boundingInfo;
+                Prim2DBase.boundinbBoxReentrency = false;
+
+                return this._boundingSize;
+
+            }
+
             return this._size;
         }
 
@@ -1512,7 +1576,7 @@
             }
 
             this.size.width = value;
-            this.markAsDirty("actualSize");
+            this.markAsDirty("size");
             this._positioningDirty();
         }
 
@@ -1538,7 +1602,7 @@
             }
 
             this.size.height = value;
-            this.markAsDirty("actualSize");
+            this.markAsDirty("size");
             this._positioningDirty();
         }
 
@@ -1739,7 +1803,7 @@
 
         public get layoutEngine(): LayoutEngineBase {
             if (!this._layoutEngine) {
-                this._layoutEngine = new CanvasLayoutEngine();
+                this._layoutEngine = CanvasLayoutEngine.Singleton;
             }
             return this._layoutEngine;
         }
@@ -1752,6 +1816,30 @@
             this._changeLayoutEngine(value);
         }
 
+        public get layoutArea(): Size {
+            return this._layoutArea;
+        }
+
+        public set layoutArea(val: Size) {
+            if (this._layoutArea.equals(val)) {
+                return;
+            }
+            this._positioningDirty();
+            this._layoutArea = val;
+        }
+
+        public get layoutAreaPos(): Vector2 {
+            return this._layoutAreaPos;
+        }
+
+        public set layoutAreaPos(val: Vector2) {
+            if (this._layoutAreaPos.equals(val)) {
+                return;
+            }
+            this._positioningDirty();
+            this._layoutAreaPos = val;
+        }
+
         /**
          * Define if the Primitive can be subject to intersection test or not (default is true)
          */
@@ -1814,6 +1902,10 @@
                     bi.unionToRef(tps, bi);
                 }
 
+                this._boundingSize = Size.Zero();
+                let m = this._boundingInfo.max();
+                this._boundingSize = new Size((!this._size || this._size.width == null) ? Math.ceil(m.x) : this._size.width, (!this._size || this._size.height == null) ? Math.ceil(m.y) : this._size.height);
+
                 this._clearFlags(SmartPropertyPrim.flagBoundingInfoDirty);
             }
             return this._boundingInfo;
@@ -1825,7 +1917,7 @@
          * @returns true if the size is automatically computed, false if it were manually specified.
          */
         public get isSizeAuto(): boolean {
-            return this.size == null;
+            return this._size == null;
         }
 
         /**
@@ -1980,6 +2072,7 @@
 
         private addChild(child: Prim2DBase) {
             child._parent = this;
+            this._boundingBoxDirty();
             this._patchHierarchyDepth(child);
             this._children.push(child);
         }
@@ -2109,7 +2202,7 @@
         }
 
         private _changeLayoutEngine(engine: LayoutEngineBase) {
-            
+            this._layoutEngine = engine;
         }
 
         private static _t0: Matrix = new Matrix();
@@ -2121,7 +2214,7 @@
             let parentMarginOffsetChanged = false;
             let parentMarginOffset: Vector2 = null;
             if (this._parent) {
-                parentMarginOffset = this._parent._paddingOffset;
+                parentMarginOffset = this._parent._marginOffset;
                 parentMarginOffsetChanged = !parentMarginOffset.equals(this._parentMargingOffset);
                 this._parentMargingOffset.copyFrom(parentMarginOffset);
             } else {
@@ -2174,13 +2267,16 @@
             // Otherwise positioning will take care of it.
             let sizeDirty = this.checkPropertiesDirty(Prim2DBase.sizeProperty.flagId);
             if (!this._isFlagSet(SmartPropertyPrim.flagLayoutDirty) && sizeDirty) {
-                if (this.size.width != null) {
-                    this.actualSize.width = this.size.width;
-                }
-                if (this.size.height != null) {
-                    this.actualSize.height = this.size.height;
+                let size = this.size;
+                if (size) {
+                    if (this.size.width != null) {
+                        this.actualSize.width = this.size.width;
+                    }
+                    if (this.size.height != null) {
+                        this.actualSize.height = this.size.height;
+                    }
+                    this.clearPropertiesDirty(Prim2DBase.sizeProperty.flagId);
                 }
-                this.clearPropertiesDirty(Prim2DBase.sizeProperty.flagId);
             }
 
             // Check for layout update
@@ -2261,9 +2357,9 @@
 
             // Apply margin
             if (this._hasMargin) {
-                this.margin.computeWithAlignment(this._layoutArea, this.size, this.marginAlignment, this._paddingOffset, Prim2DBase._size);
+                this.margin.computeWithAlignment(this.layoutArea, this.size, this.marginAlignment, this._marginOffset, Prim2DBase._size);
 
-                this.actualPosition = this._paddingOffset.clone();
+                this.actualPosition = this._marginOffset.add(this._layoutAreaPos);
 
                 if (this.size.width != null) {
                     this.size.width = Prim2DBase._size.width;
@@ -2278,15 +2374,15 @@
                 this._getInitialContentAreaToRef(this.actualSize, Prim2DBase._icPos, Prim2DBase._icArea);
                 Prim2DBase._icArea.width = Math.max(0, Prim2DBase._icArea.width);
                 Prim2DBase._icArea.height = Math.max(0, Prim2DBase._icArea.height);
-                this.padding.compute(Prim2DBase._icArea, this._paddingOffset, Prim2DBase._size);
-                this._paddingOffset.x += Prim2DBase._icPos.x;
-                this._paddingOffset.y += Prim2DBase._icPos.y;
+                this.padding.compute(Prim2DBase._icArea, this._marginOffset, Prim2DBase._size);
+                this._marginOffset.x += Prim2DBase._icPos.x;
+                this._marginOffset.y += Prim2DBase._icPos.y;
                 this._contentArea.copyFrom(Prim2DBase._size);              
             } else {
                 this._getInitialContentAreaToRef(this.actualSize, Prim2DBase._icPos, Prim2DBase._icArea);
                 Prim2DBase._icArea.width = Math.max(0, Prim2DBase._icArea.width);
                 Prim2DBase._icArea.height = Math.max(0, Prim2DBase._icArea.height);
-                this._paddingOffset.copyFrom(Prim2DBase._icPos);
+                this._marginOffset.copyFrom(Prim2DBase._icPos);
                 this._contentArea.copyFrom(Prim2DBase._icArea);
             }
         }
@@ -2372,14 +2468,16 @@
         private   _actualPosition        : Vector2;
         protected _size                  : Size;
         protected _actualSize            : Size;
+        public    _boundingSize          : Size;
         protected _minSize               : Size;
         protected _maxSize               : Size;
         protected _desiredSize           : Size;
         private   _layoutEngine          : LayoutEngineBase;
-        private   _paddingOffset         : Vector2;
+        private   _marginOffset          : Vector2;
         private   _parentMargingOffset   : Vector2;
         private   _parentContentArea     : Size;
-        public    _layoutArea            : Size;
+        private   _layoutAreaPos         : Vector2;
+        private   _layoutArea            : Size;
         private   _contentArea           : Size;
         private   _rotation              : number;
         private   _scale                 : number;

+ 13 - 21
src/Canvas2d/babylon.rectangle2d.ts

@@ -171,21 +171,12 @@
         public static notRoundedProperty: Prim2DPropInfo;
         public static roundRadiusProperty: Prim2DPropInfo;
 
-//        @instanceLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 1, pi => Rectangle2D.sizeProperty = pi, false, true)
-        public get size(): Size {
-            return this._size;
-        }
-
-        public set size(value: Size) {
-            this._size = value;
-        }
-
         @instanceLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 1, pi => Rectangle2D.actualSizeProperty = pi, false, true)
         public get actualSize(): Size {
             if (this._actualSize) {
                 return this._actualSize;
             }
-            return this._size;
+            return this.size;
         }
 
         public set actualSize(value: Size) {
@@ -289,16 +280,9 @@
         }
 
         protected updateLevelBoundingInfo() {
-            BoundingInfo2D.CreateFromSizeToRef(this.size, this._levelBoundingInfo);
+            BoundingInfo2D.CreateFromSizeToRef(this.actualSize, this._levelBoundingInfo);
         }
 
-        //protected setupRectangle2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, size: Size, roundRadius, fill: IBrush2D, border: IBrush2D, borderThickness: number, isVisible: boolean, marginTop: number | string, marginLeft: number | string, marginRight: number | string, marginBottom: number | string, vAlignment: number, hAlignment: number) {
-        //    this.setupShape2D(owner, parent, id, position, origin, isVisible, fill, border, borderThickness, marginTop, marginLeft, marginRight, marginBottom, hAlignment, vAlignment);
-        //    this.size = size;
-        //    this.notRounded = !roundRadius;
-        //    this.roundRadius = roundRadius;
-        //}
-
         /**
          * Create an Rectangle 2D Shape primitive. May be a sharp rectangle (with sharp corners), or a rounded one.
          * @param parent the parent primitive, must be a valid primitive (or the Canvas)
@@ -338,7 +322,7 @@
             marginLeft       ?: number | string,
             marginRight      ?: number | string,
             marginBottom     ?: number | string,
-            margin           ?: string,
+            margin           ?: number | string,
             marginHAlignment ?: number,
             marginVAlignment ?: number,
             marginAlignment  ?: string,
@@ -358,11 +342,19 @@
 
             super(settings);
 
-            let size            = settings.size || (new Size((settings.width === null) ? null : (settings.width || 10), (settings.height === null) ? null : (settings.height || 10)));
+            if (settings.size != null) {
+                this.size = settings.size;
+            }
+            else if (settings.width || settings.height) {
+                let size = new Size(settings.width, settings.height);
+                this.size = size;
+            }
+
+            //let size            = settings.size || (new Size((settings.width === null) ? null : (settings.width || 10), (settings.height === null) ? null : (settings.height || 10)));
             let roundRadius     = (settings.roundRadius == null) ? 0 : settings.roundRadius;
             let borderThickness = (settings.borderThickness == null) ? 1 : settings.borderThickness;
 
-            this.size            = size;
+            //this.size            = size;
             this.roundRadius     = roundRadius;
             this.borderThickness = borderThickness;
         }

+ 33 - 18
src/Canvas2d/babylon.smartPropertyPrim.ts

@@ -313,27 +313,35 @@
             this._handlePropChanged(undefined, newValue, propertyName, propInfo, propInfo.typeLevelCompare);
         }
 
-        private _handlePropChanged<T>(curValue: T, newValue: T, propName: string, propInfo: Prim2DPropInfo, typeLevelCompare: boolean) {
-            // If the property change also dirty the boundingInfo, update the boundingInfo dirty flags
-            if (propInfo.dirtyBoundingInfo) {
-                this._setFlags(SmartPropertyPrim.flagLevelBoundingInfoDirty);
-
-                // Escalate the dirty flag in the instance hierarchy, stop when a renderable group is found or at the end
-                if (this instanceof Prim2DBase) {
-                    let curprim = (<any>this).parent;
-                    while (curprim) {
-                        curprim._boundingInfoDirty = true;
-
-                        if (curprim instanceof Group2D) {
-                            if (curprim.isRenderableGroup) {
-                                break;
-                            }
-                        }
+        protected _boundingBoxDirty() {
+            this._setFlags(SmartPropertyPrim.flagLevelBoundingInfoDirty);
+
+            // Escalate the dirty flag in the instance hierarchy, stop when a renderable group is found or at the end
+            if (this instanceof Prim2DBase) {
+                let curprim: Prim2DBase = (<any>this);
+                while (curprim) {
+                    curprim._boundingSize = null;
+                    curprim._setFlags(SmartPropertyPrim.flagBoundingInfoDirty);
+                    if (curprim.isSizeAuto) {
+                        curprim.onPrimitivePropertyDirty(Prim2DBase.sizeProperty.flagId);
+                    }
 
-                        curprim = curprim.parent;
+                    if (curprim instanceof Group2D) {
+                        if (curprim.isRenderableGroup) {
+                            break;
+                        }
                     }
+
+                    curprim = curprim.parent;
                 }
             }
+        }
+
+        private _handlePropChanged<T>(curValue: T, newValue: T, propName: string, propInfo: Prim2DPropInfo, typeLevelCompare: boolean) {
+            // If the property change also dirty the boundingInfo, update the boundingInfo dirty flags
+            if (propInfo.dirtyBoundingInfo) {
+                this._boundingBoxDirty();
+            }
 
             // Trigger property changed
             let info = SmartPropertyPrim.propChangedInfo;
@@ -348,6 +356,14 @@
                 this.handleGroupChanged(propInfo);
             }
 
+            // Check for parent layout dirty
+            if (this instanceof Prim2DBase) {
+                let p = (<any>this)._parent;
+                if (p != null && p.layoutEngine && (p.layoutEngine.layoutDirtyOnPropertyChangedMask & propInfo.flagId) !== 0) {
+                    p._setLayoutDirty();
+                }
+            }
+
             // For type level compare, if there's a change of type it's a change of model, otherwise we issue an instance change
             var instanceDirty = false;
             if (typeLevelCompare && curValue != null && newValue != null) {
@@ -401,7 +417,6 @@
 
         /**
          * Retrieve the boundingInfo for this Primitive, computed based on the primitive itself and NOT its children
-         * @returns {} 
          */
         public get levelBoundingInfo(): BoundingInfo2D {
             if (this._isFlagSet(SmartPropertyPrim.flagLevelBoundingInfoDirty)) {

+ 2 - 2
src/Canvas2d/babylon.sprite2d.ts

@@ -141,7 +141,7 @@
             if (this._actualSize) {
                 return this._actualSize;
             }
-            return this._size;
+            return this.size;
         }
 
         public set actualSize(value: Size) {
@@ -236,7 +236,7 @@
             marginLeft       ?: number | string,
             marginRight      ?: number | string,
             marginBottom     ?: number | string,
-            margin           ?: string,
+            margin           ?: number | string,
             marginHAlignment ?: number,
             marginVAlignment ?: number,
             marginAlignment  ?: string,

+ 1 - 1
src/Canvas2d/babylon.text2d.ts

@@ -249,7 +249,7 @@
             marginLeft       ?: number | string,
             marginRight      ?: number | string,
             marginBottom     ?: number | string,
-            margin           ?: string,
+            margin           ?: number | string,
             marginHAlignment ?: number,
             marginVAlignment ?: number,
             marginAlignment  ?: string,