Explorar o código

Canvas 2D Breaking changes

 - brush can be initialized from string
 - alignment can be initialized from string

 - origin is no longer used to compute positioning of Primitive, they are always relative to their bottom/left corner.

 - actualPosition read-only property is introduced and should be used the same way as actualSize. Position is now more about a property used by the CanvasLayoutEngine

 - interaction: Enter/Leave are now correctly spread
nockawa %!s(int64=9) %!d(string=hai) anos
pai
achega
2a9ee8c73a

+ 89 - 30
src/Canvas2d/babylon.canvas2d.ts

@@ -58,8 +58,8 @@
             origin                    ?: Vector2,
             isVisible                 ?: boolean,
             backgroundRoundRadius     ?: number,
-            backgroundFill            ?: IBrush2D,
-            backgroundBorder          ?: IBrush2D,
+            backgroundFill            ?: IBrush2D | string,
+            backgroundBorder          ?: IBrush2D | string,
             backgroundBorderThickNess ?: number,
         }) {
             super(settings);
@@ -83,7 +83,7 @@
                 }
 
                 if (settings.backgroundBorder != null) {
-                    this.backgroundBorder = settings.backgroundBorder;
+                    this.backgroundBorder = <IBrush2D>settings.backgroundBorder;        // TOFIX
                 }
 
                 if (settings.backgroundBorderThickNess != null) {
@@ -91,7 +91,7 @@
                 }
 
                 if (settings.backgroundFill != null) {
-                    this.backgroundFill = settings.backgroundFill;
+                    this.backgroundFill = <IBrush2D>settings.backgroundFill;
                 }
 
                 this._background._patchHierarchy(this);
@@ -225,8 +225,8 @@
             let res = new Vector2(v.x*rsf, v.y*rsf);
             let size = this.actualSize;
             let o = this.origin;
-            res.x += size.width * o.x;
-            res.y += size.width * o.y;
+            res.x += size.width * 0.5;  // res is centered, make it relative to bottom/left
+            res.y += size.width * 0.5;
             return res;
         }
 
@@ -350,8 +350,8 @@
                 var x = localPosition.x - viewport.x;
                 var y = localPosition.y - viewport.y;
 
-                pii.canvasPointerPos.x = x - this.position.x;
-                pii.canvasPointerPos.y = engine.getRenderHeight() -y - this.position.y;
+                pii.canvasPointerPos.x = x - this.actualPosition.x;
+                pii.canvasPointerPos.y = engine.getRenderHeight() -y - this.actualPosition.y;
             } else {
                 pii.canvasPointerPos.x = localPosition.x;
                 pii.canvasPointerPos.y = localPosition.y;
@@ -479,9 +479,9 @@
         private _bubbleNotifyPrimPointerObserver(prim: Prim2DBase, mask: number, eventData: any) {
             let ppi = this._primPointerInfo;
 
-            // In case of PointerOver/Out we will first notify the children (but the deepest to the closest) with PointerEnter/Leave
+            // In case of PointerOver/Out we will first notify the parent with PointerEnter/Leave
             if ((mask & (PrimitivePointerInfo.PointerOver | PrimitivePointerInfo.PointerOut)) !== 0) {
-                this._notifChildren(prim, mask);
+                this._notifParents(prim, mask);
             }
 
             let bubbleCancelled = false;
@@ -623,27 +623,27 @@
             }
         }
 
-        _notifChildren(prim: Prim2DBase, mask: number) {
+        _notifParents(prim: Prim2DBase, mask: number) {
             let pii = this._primPointerInfo;
 
-            prim.children.forEach(curChild => {
-                // Recurse first, we want the deepest to be notified first
-                this._notifChildren(curChild, mask);
+            let curPrim: Prim2DBase = this;
 
-                this._updatePrimPointerPos(curChild);
+            while (curPrim) {
+                this._updatePrimPointerPos(curPrim);
 
                 // Fire the proper notification
                 if (mask === PrimitivePointerInfo.PointerOver) {
-                    this._debugExecObserver(curChild, PrimitivePointerInfo.PointerEnter);
-                    curChild._pointerEventObservable.notifyObservers(pii, PrimitivePointerInfo.PointerEnter);
+                    this._debugExecObserver(curPrim, PrimitivePointerInfo.PointerEnter);
+                    curPrim._pointerEventObservable.notifyObservers(pii, PrimitivePointerInfo.PointerEnter);
                 }
 
                 // Trigger a PointerLeave corresponding to the PointerOut
                 else if (mask === PrimitivePointerInfo.PointerOut) {
-                    this._debugExecObserver(curChild, PrimitivePointerInfo.PointerLeave);
-                    curChild._pointerEventObservable.notifyObservers(pii, PrimitivePointerInfo.PointerLeave);
+                    this._debugExecObserver(curPrim, PrimitivePointerInfo.PointerLeave);
+                    curPrim._pointerEventObservable.notifyObservers(pii, PrimitivePointerInfo.PointerLeave);
                 }
-            });
+                curPrim = curPrim.parent;
+            }
         }
 
         /**
@@ -893,7 +893,7 @@
                 }
 
                 // Dirty the Layout at the Canvas level to recompute as the size changed
-                this._setFlags(SmartPropertyPrim.flagLayoutDirty);
+                this._setLayoutDirty();
             }
 
             var context = new PrepareRender2DContext();
@@ -989,7 +989,7 @@
 
                 // Create a Sprite that will be used to render this cache, the "__cachedSpriteOfGroup__" starting id is a hack to bypass exception throwing in case of the Canvas doesn't normally allows direct primitives
                 else {
-                    let sprite = new Sprite2D(map, { parent: parent, id:`__cachedSpriteOfGroup__${group.id}`, x: group.position.x, y: group.position.y, spriteSize:node.contentSize, spriteLocation:node.pos});
+                    let sprite = new Sprite2D(map, { parent: parent, id:`__cachedSpriteOfGroup__${group.id}`, x: group.actualPosition.x, y: group.actualPosition.y, spriteSize:node.contentSize, spriteLocation:node.pos});
                     sprite.origin = group.origin.clone();
                     res.sprite = sprite;
                 }
@@ -1025,6 +1025,65 @@
             return Canvas2D._gradientColorBrushes.getOrAddWithFactory(GradientColorBrush2D.BuildKey(color1, color2, translation, rotation, scale), () => new GradientColorBrush2D(color1, color2, translation, rotation, scale, true));
         }
 
+        public static GetBrushFromString(brushString: string): IBrush2D {
+            // Note: yes, I hate/don't know RegEx.. Feel free to add your contribution to the cause!
+
+            brushString = brushString.trim();
+            let split = brushString.split(",");
+
+            // Solid, formatted as: "[solid:]#FF808080"
+            if (split.length === 1) {
+                let value: string = null;
+                if (brushString.indexOf("solid:") === 0) {
+                    value = brushString.substr(6).trim();
+                } else if (brushString.indexOf("#") === 0) {
+                    value = brushString;
+                } else {
+                    return null;
+                }
+                return Canvas2D.GetSolidColorBrushFromHex(value);
+            }
+
+            // Gradient, formatted as: "[gradient:]#FF808080, #FFFFFFF[, [10:20], 180, 1]" [10:20] is a real formatting expected not a EBNF notation
+            // Order is: gradient start, gradient end, translation, rotation (degree), scale
+            else {
+                if (split[0].indexOf("gradient:") === 0) {
+                    split[0] = split[0].substr(9).trim();
+                }
+
+                try {
+                    let start = Color4.FromHexString(split[0].trim());
+                    let end = Color4.FromHexString(split[1].trim());
+
+                    let t: Vector2 = Vector2.Zero();
+                    if (split.length > 2) {
+                        let v = split[2].trim();
+                        if (v.charAt(0) !== "[" || v.charAt(v.length - 1) !== "]") {
+                            return null;
+                        }
+                        let sep = v.indexOf(":");
+                        let x = parseFloat(v.substr(1, sep));
+                        let y = parseFloat(v.substr(sep + 1, v.length - (sep + 1)));
+                        t = new Vector2(x, y);
+                    }
+
+                    let r: number = 0;
+                    if (split.length > 3) {
+                        r = Tools.ToRadians(parseFloat(split[3].trim()));
+                    }
+
+                    let s: number = 1;
+                    if (split.length > 4) {
+                        s = parseFloat(split[4].trim());
+                    }
+
+                    return Canvas2D.GetGradientColorBrush(start, end, t, r, s);
+                } catch (e) {
+                    return null;
+                } 
+            }
+        }
+
         private static _solidColorBrushes: StringDictionary<IBrush2D> = new StringDictionary<IBrush2D>();
         private static _gradientColorBrushes: StringDictionary<IBrush2D> = new StringDictionary<IBrush2D>();
     }
@@ -1052,16 +1111,16 @@
 
             children                 ?: Array<Prim2DBase>,
             id                       ?: string,
-            position                 ?: Vector3,
-            rotation                 ?: Quaternion,
+            worldPosition            ?: Vector3,
+            worldRotation            ?: Quaternion,
             renderScaleFactor        ?: number,
             sideOrientation          ?: number,
             cachingStrategy          ?: number,
             enableInteraction        ?: boolean,
             isVisible                ?: boolean,
             backgroundRoundRadius    ?: number,
-            backgroundFill           ?: IBrush2D,
-            backgroundBorder         ?: IBrush2D,
+            backgroundFill           ?: IBrush2D | string,
+            backgroundBorder         ?: IBrush2D | string,
             backgroundBorderThickNess?: number,
             customWorldSpaceNode     ?: Node,
             marginTop                ?: number | string,
@@ -1116,8 +1175,8 @@
                 mtl.specularColor = new Color3(0, 0, 0);
                 mtl.disableLighting = true;
                 mtl.useAlphaFromDiffuseTexture = true;
-                plane.position = settings && settings.position || Vector3.Zero();
-                plane.rotationQuaternion = settings && settings.rotation || Quaternion.Identity();
+                plane.position = settings && settings.worldPosition || Vector3.Zero();
+                plane.rotationQuaternion = settings && settings.worldRotation || Quaternion.Identity();
                 plane.material = mtl;
                 this._worldSpaceNode = plane;
             } else {
@@ -1161,8 +1220,8 @@
             enableInteraction         ?: boolean,
             isVisible                 ?: boolean,
             backgroundRoundRadius     ?: number,
-            backgroundFill            ?: IBrush2D,
-            backgroundBorder          ?: IBrush2D,
+            backgroundFill            ?: IBrush2D | string,
+            backgroundBorder          ?: IBrush2D | string,
             backgroundBorderThickNess ?: number,
             marginTop                 ?: number | string,
             marginLeft                ?: number | string,

+ 1 - 4
src/Canvas2d/babylon.canvas2dLayoutEngine.ts

@@ -27,15 +27,12 @@
             // 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)) {
-                this._doUpdate(prim);
-                prim._clearFlags(SmartPropertyPrim.flagLayoutDirty);
 
                 // Recurse
                 for (let child of prim.children) {
                     this._doUpdate(child);
-                    child._clearFlags(SmartPropertyPrim.flagLayoutDirty);
-                    this.updateLayout(child);
                 }
+                prim._clearFlags(SmartPropertyPrim.flagLayoutDirty);
             }
 
         }

+ 7 - 16
src/Canvas2d/babylon.ellipse2d.ts

@@ -191,23 +191,17 @@
         }
 
         protected levelIntersect(intersectInfo: IntersectInfo2D): boolean {
-            let x = intersectInfo._localPickPosition.x;
-            let y = intersectInfo._localPickPosition.y;
-            let w = this.size.width/2;
-            let h = this.size.height/2;
+            let w = this.size.width / 2;
+            let h = this.size.height / 2;
+            let x = intersectInfo._localPickPosition.x-w;
+            let y = intersectInfo._localPickPosition.y-h;
             return ((x * x) / (w * w) + (y * y) / (h * h)) <= 1;
         }
 
         protected updateLevelBoundingInfo() {
-            BoundingInfo2D.CreateFromSizeToRef(this.size, this._levelBoundingInfo, this.origin);
+            BoundingInfo2D.CreateFromSizeToRef(this.size, this._levelBoundingInfo);
         }
 
-        //protected setupEllipse2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, size: Size, subdivisions: number, 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.subdivisions = subdivisions;
-        //}
-
         /**
          * Create an Ellipse 2D Shape primitive
          * @param parent the parent primitive, must be a valid primitive (or the Canvas)
@@ -238,8 +232,8 @@
             width             ?: number,
             height            ?: number,
             subdivisions      ?: number,
-            fill              ?: IBrush2D,
-            border            ?: IBrush2D,
+            fill              ?: IBrush2D | string,
+            border            ?: IBrush2D | string,
             borderThickness   ?: number,
             isVisible         ?: boolean,
             marginTop         ?: number | string,
@@ -257,7 +251,6 @@
             padding           ?: string,
             paddingHAlignment ?: number,
             paddingVAlignment ?: number,
-            paddingAlignment  ?: string,
 
         }) {
 
@@ -268,11 +261,9 @@
 
             super(settings);
 
-            let pos  = settings.position || new Vector2(settings.x || 0, settings.y || 0);
             let size = settings.size || (new Size(settings.width || 10, settings.height || 10));
             let sub  = (settings.subdivisions == null) ? 64 : settings.subdivisions;
 
-            this.position     = pos;
             this.size         = size;
             this.subdivisions = sub;
         }

+ 6 - 14
src/Canvas2d/babylon.group2d.ts

@@ -65,13 +65,14 @@
             padding           ?: string,
             paddingHAlignment ?: number,
             paddingVAlignment ?: number,
-            paddingAlignment  ?: string,
 
         }) {
             if (settings == null) {
                 settings = {};
             }
-
+            if (settings.origin == null) {
+                settings.origin = new Vector2(0, 0);
+            }
             super(settings);
  
             let size = (!settings.size && !settings.width && !settings.height) ? null : (settings.size || (new Size(settings.width || 0, settings.height || 0)));
@@ -83,8 +84,6 @@
 
         static _createCachedCanvasGroup(owner: Canvas2D): Group2D {
             var g = new Group2D({ parent: owner, id: "__cachedCanvasGroup__", position: Vector2.Zero(), origin: Vector2.Zero(), size:null, isVisible:true});
-            //g.setupGroup2D(owner, null, "__cachedCanvasGroup__", Vector2.Zero(), Vector2.Zero(), null, true, Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY, null, null, null, null, null, null);
-            //g.origin = Vector2.Zero();
             return g;
             
         }
@@ -143,13 +142,6 @@
             return true;
         }
 
-        //protected setupGroup2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, size: Size, isVisible: boolean, cacheBehavior: number, marginTop: number | string, marginLeft: number | string, marginRight: number | string, marginBottom: number | string, hAlign: number, vAlign: number) {
-        //    this.setupPrim2DBase(owner, parent, id, position, origin, isVisible, marginTop, marginLeft, marginRight, marginBottom , hAlign, vAlign);
-        //    this._cacheBehavior = cacheBehavior;
-        //    this.size = size;
-        //    this._viewportPosition = Vector2.Zero();
-        //}
-
         /**
          * @returns Returns true if the Group render content, false if it's a logical group only
          */
@@ -203,7 +195,7 @@
 
             // Compare the size with the one we previously had, if it differs we set the property dirty and trigger a GroupChanged to synchronize a displaySprite (if any)
             if (!actualSize.equals(this._actualSize)) {
-                this._instanceDirtyFlags |= Group2D.actualSizeProperty.flagId;
+                this.onPrimitivePropertyDirty(Group2D.actualSizeProperty.flagId);
                 this._actualSize = actualSize;
                 this.handleGroupChanged(Group2D.actualSizeProperty);
             }
@@ -688,8 +680,8 @@
 
             // For now we only support these property changes
             // TODO: add more! :)
-            if (prop.id === Prim2DBase.positionProperty.id) {
-                rd._cacheRenderSprite.position = this.position.clone();
+            if (prop.id === Prim2DBase.actualPositionProperty.id) {
+                rd._cacheRenderSprite.actualPosition = this.actualPosition.clone();
             } else if (prop.id === Prim2DBase.rotationProperty.id) {
                 rd._cacheRenderSprite.rotation = this.rotation;
             } else if (prop.id === Prim2DBase.scaleProperty.id) {

+ 16 - 22
src/Canvas2d/babylon.lines2d.ts

@@ -157,12 +157,12 @@
             super(partId, 1);
         }
 
-        @instanceData()
+        @instanceData(Shape2D.SHAPE2D_CATEGORY_FILLGRADIENT)
         get boundingMin(): Vector2 {
             return null;
         }
 
-        @instanceData()
+        @instanceData(Shape2D.SHAPE2D_CATEGORY_FILLGRADIENT)
         get boundingMax(): Vector2 {
             return null;
         }
@@ -324,21 +324,12 @@
         }
 
         protected updateLevelBoundingInfo() {
-            BoundingInfo2D.CreateFromMinMaxToRef(this._boundingMin.x, this._boundingMax.x, this._boundingMin.y, this._boundingMax.y, this._levelBoundingInfo, this.origin);
+            if (!this._boundingMin) {
+                this._computeLines2D();
+            }
+            BoundingInfo2D.CreateFromMinMaxToRef(this._boundingMin.x, this._boundingMax.x, this._boundingMin.y, this._boundingMax.y, this._levelBoundingInfo);
         }
 
-        //protected setupLines2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, points: Vector2[], fillThickness: number, startCap: number, endCap: number, fill: IBrush2D, border: IBrush2D, borderThickness: number, closed: boolean, 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.fillThickness = fillThickness;
-        //    this.startCap = startCap;
-        //    this.endCap = endCap;
-        //    this.points = points;
-        //    this.closed = closed;
-        //    this._size = Size.Zero();
-        //    this._boundingMin = Vector2.Zero();
-        //    this._boundingMax = Vector2.Zero();
-        //}
-
         /**
          * Create an 2D Lines Shape primitive. The defined lines may be opened or closed (see below)
          * @param parent the parent primitive, must be a valid primitive (or the Canvas)
@@ -372,8 +363,8 @@
             closed           ?: boolean,
             startCap         ?: number,
             endCap           ?: number,
-            fill             ?: IBrush2D,
-            border           ?: IBrush2D,
+            fill             ?: IBrush2D | string,
+            border           ?: IBrush2D | string,
             borderThickness  ?: number,
             isVisible        ?: boolean,
             marginTop        ?: number | string,
@@ -391,7 +382,6 @@
             padding          ?: string,
             paddingHAlignment?: number,
             paddingVAlignment?: number,
-            paddingAlignment ?: string,
         }) {
 
             if (!settings) {
@@ -1139,13 +1129,17 @@
             }
             if (part.id === Shape2D.SHAPE2D_BORDERPARTID) {
                 let d = <Lines2DInstanceData>part;
-                d.boundingMin = this.boundingMin;
-                d.boundingMax = this.boundingMax;
+                if (this.border instanceof GradientColorBrush2D) {
+                    d.boundingMin = this.boundingMin;
+                    d.boundingMax = this.boundingMax;
+                }
             }
             else if (part.id === Shape2D.SHAPE2D_FILLPARTID) {
                 let d = <Lines2DInstanceData>part;
-                d.boundingMin = this.boundingMin;
-                d.boundingMax = this.boundingMax;
+                if (this.fill instanceof GradientColorBrush2D) {
+                    d.boundingMin = this.boundingMin;
+                    d.boundingMax = this.boundingMax;
+                }
             }
             return true;
         }

+ 225 - 170
src/Canvas2d/babylon.prim2dBase.ts

@@ -295,7 +295,7 @@
         constructor(changeCallback: () => void) {
             this._changedCallback = changeCallback;
             this._horizontal = PrimitiveAlignment.AlignLeft;
-            this._vertical = PrimitiveAlignment.AlignTop;
+            this._vertical = PrimitiveAlignment.AlignBottom;
         }
 
         public static get AlignLeft():    number { return PrimitiveAlignment._AlignLeft;   }
@@ -848,7 +848,7 @@
             }
         }
 
-        public compute(sourceArea: Size, contentSize: Size, alignment: PrimitiveAlignment, dstOffset: Vector2, dstArea: Size) {
+        public computeWithAlignment(sourceArea: Size, contentSize: Size, alignment: PrimitiveAlignment, dstOffset: Vector2, dstArea: Size) {
             // Fetch some data
             let topType      = this._getType(0, true);
             let leftType     = this._getType(1, true);
@@ -879,7 +879,7 @@
                 case PrimitiveAlignment.AlignRight:
                 {
                     if (isRightAuto) {
-                        dstOffset.x = sourceArea.width - width;
+                        dstOffset.x = Math.round(sourceArea.width - width);
                     } else {
                         this._computePixels(2, rightType, sourceArea, true);
                         dstOffset.x = Math.round(sourceArea.width - (width + this.rightPixels));
@@ -889,35 +889,19 @@
                 }
                 case PrimitiveAlignment.AlignStretch:
                 {
-                    if (hasWidth) {
-                        let left = 0;
-                        if (!isLeftAuto) {
-                            this._computePixels(1, leftType, sourceArea, true);
-                            left = this.leftPixels;
-                        }
-                        let right = 0;
-                        if (!isRightAuto) {
-                            this._computePixels(2, rightType, sourceArea, true);
-                            right = this.rightPixels;
-                        }
-                        let offset = left - right;
-                        dstOffset.x = Math.round(((sourceArea.width - width) / 2) + offset);
-                        dstArea.width = width;
+                    if (isLeftAuto) {
+                        dstOffset.x = 0;
                     } else {
-                        if (isLeftAuto) {
-                            dstOffset.x = 0;
-                        } else {
-                            this._computePixels(1, leftType, sourceArea, true);
-                            dstOffset.x = this.leftPixels;
-                        }
-
-                        let right = 0;
-                        if (!isRightAuto) {
-                            this._computePixels(2, rightType, sourceArea, true);
-                            right = this.rightPixels;
-                        }
-                        dstArea.width = sourceArea.width - (dstOffset.x + right);
+                        this._computePixels(1, leftType, sourceArea, true);
+                        dstOffset.x = this.leftPixels;
                     }
+
+                    let right = 0;
+                    if (!isRightAuto) {
+                        this._computePixels(2, rightType, sourceArea, true);
+                        right = this.rightPixels;
+                    }
+                    dstArea.width = sourceArea.width - (dstOffset.x + right);
                     break;
                 }
                 case PrimitiveAlignment.AlignCenter:
@@ -963,35 +947,19 @@
                 }
                 case PrimitiveAlignment.AlignStretch:
                 {
-                    if (hasHeight) {
-                        let top = 0;
-                        if (!isTopAuto) {
-                            this._computePixels(0, topType, sourceArea, true);
-                            top = this.topPixels;
-                        }
-                        let bottom = 0;
-                        if (!isBottomAuto) {
-                            this._computePixels(3, bottomType, sourceArea, true);
-                            bottom = this.bottomPixels;
-                        }
-                        let offset = bottom - top;
-                        dstOffset.y = Math.round(((sourceArea.height - height) / 2) + offset);
-                        dstArea.height = height;
+                    if (isBottomAuto) {
+                        dstOffset.y = 0;
                     } else {
-                        if (isBottomAuto) {
-                            dstOffset.y = 0;
-                        } else {
-                            this._computePixels(3, bottomType, sourceArea, true);
-                            dstOffset.y = this.bottomPixels;
-                        }
-
-                        let top = 0;
-                        if (!isTopAuto) {
-                            this._computePixels(0, topType, sourceArea, true);
-                            top = this.topPixels;
-                        }
-                        dstArea.height = sourceArea.height - (dstOffset.y + top);
+                        this._computePixels(3, bottomType, sourceArea, true);
+                        dstOffset.y = this.bottomPixels;
+                    }
+
+                    let top = 0;
+                    if (!isTopAuto) {
+                        this._computePixels(0, topType, sourceArea, true);
+                        top = this.topPixels;
                     }
+                    dstArea.height = sourceArea.height - (dstOffset.y + top);
                     break;
                 }
                 case PrimitiveAlignment.AlignCenter:
@@ -1010,6 +978,37 @@
                 }
             }
         }
+
+        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;
+
+            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 = isLeftAuto ? 0 : this.leftPixels;
+            dstArea.width = sourceArea.width - (dstOffset.x + (isRightAuto ? 0 : this.rightPixels));
+
+            dstOffset.y = isTopAuto ? 0 : this.topPixels;
+            dstArea.height = sourceArea.height - (dstOffset.y + (isTopAuto ? 0 : this.bottomPixels));
+        }
     }
 
     /**
@@ -1093,6 +1092,8 @@
             position            ?: Vector2,
             x                   ?: number,
             y                   ?: number,
+            rotation            ?: number,
+            scale               ?: number,
             origin              ?: Vector2,
             isVisible           ?: boolean,
             marginTop           ?: number | string,
@@ -1108,9 +1109,6 @@
             paddingRight        ?: number | string,
             paddingBottom       ?: number | string,
             padding             ?: string,
-            paddingHAlignment   ?: number,
-            paddingVAlignment   ?: number,
-            paddingAlignment    ?: string,
         }) {
 
             // Avoid checking every time if the object exists
@@ -1147,7 +1145,6 @@
             this._size                       = Size.Zero();
             this._layoutArea                 = Size.Zero();
             this._paddingOffset              = Vector2.Zero();
-            this._paddingArea                = Size.Zero();
             this._margingOffset              = Vector2.Zero();
             this._parentMargingOffset        = Vector2.Zero();
             this._parentContentArea          = Size.Zero();
@@ -1160,13 +1157,12 @@
             this._margin                     = null;
             this._padding                    = null;
             this._marginAlignment            = null;
-            this._paddingAlignment           = null;
             this._id                         = settings.id;
             this.propertyChanged             = new Observable<PropertyChangedInfo>();
             this._children                   = new Array<Prim2DBase>();
+            this._localTransform             = new Matrix();
             this._globalTransform            = null;
             this._invGlobalTransform         = null;
-            this._localTransform             = null;
             this._globalTransformProcessStep = 0;
             this._globalTransformStep        = 0;
             this._hierarchyDepth             = 0;
@@ -1196,10 +1192,16 @@
             }
 
             // Set the model related properties
-            let pos = settings.position || new Vector2(settings.x || 0, settings.y || 0);
-            this.position = pos;
-            this.rotation = 0;
-            this.scale = 1;
+            if (settings.position != null) {
+                this.position = settings.position;
+            }
+            else if (settings.x != null || settings.y != null) {
+                this.position = new Vector2(settings.x || 0, settings.y || 0);
+            } else {
+                this._position = null;
+            }
+            this.rotation = (settings.rotation == null) ? 0 : settings.rotation;
+            this.scale = (settings.scale==null) ? 1 : settings.scale;
             this.levelVisible = (settings.isVisible==null) ? true : settings.isVisible;
             this.origin = settings.origin || new Vector2(0.5, 0.5);
 
@@ -1250,18 +1252,6 @@
                 this.padding.fromString(settings.padding);
             }
 
-            if (settings.paddingHAlignment) {
-                this.paddingAlignment.horizontal = settings.paddingHAlignment;
-            }
-
-            if (settings.paddingVAlignment) {
-                this.paddingAlignment.vertical = settings.paddingVAlignment;
-            }
-
-            if (settings.paddingAlignment) {
-                this.paddingAlignment.fromString(settings.paddingAlignment);
-            }
-
             // Dirty layout and positioning
             this._parentLayoutDirty();
             this._positioningDirty();
@@ -1324,6 +1314,11 @@
         public static positionProperty: Prim2DPropInfo;
 
         /**
+         * Metadata of the actualPosition property
+         */
+        public static actualPositionProperty: Prim2DPropInfo;
+
+        /**
          * Metadata of the size property
          */
         public static sizeProperty: Prim2DPropInfo;
@@ -1369,21 +1364,54 @@
         public static paddingProperty: Prim2DPropInfo;
 
         /**
-         * Metadata of the vAlignment property
+         * Metadata of the hAlignment property
          */
-        public static paddingAlignmentProperty: Prim2DPropInfo;
+        public static marginAlignmentProperty: Prim2DPropInfo;
 
+        @instanceLevelProperty(1, pi => Prim2DBase.actualPositionProperty = pi, false, true)
         /**
-         * Metadata of the hAlignment property
+         * Return the position where the primitive is rendered in the Canvas, this position may be different than the one returned by the position property due to layout/alignment/margin/padding computing
          */
-        public static marginAlignmentProperty: Prim2DPropInfo;
+        public get actualPosition(): Vector2 {
+            if (this._actualPosition != null) {
+                return this._actualPosition;
+            }
+            if (this._position != null) {
+                return this._position;
+            }
+
+            // At least return 0,0, we can't return null on actualPosition
+            return Prim2DBase._nullPosition;
+        }
+        private static _nullPosition = Vector2.Zero();
+
+        /**
+         * DO NOT INVOKE for internal purpose only
+         */
+        public set actualPosition(val: Vector2) {
+            this._actualPosition = val;
+        }
+
+        /**
+         * Shortcut to actualPosition.x
+         */
+        public get actualX(): number {
+            return this.actualPosition.x;
+        }
+
+        /**
+         * Shortcut to actualPosition.y
+         */
+        public get actualY(): number {
+            return this.actualPosition.y;
+        }
 
-        @instanceLevelProperty(1, pi => Prim2DBase.positionProperty = pi, false, true)
         /**
          * Position of the primitive, relative to its parent.
          * BEWARE: if you change only position.x or y it won't trigger a property change and you won't have the expected behavior.
          * Use this property to set a new Vector2 object, otherwise to change only the x/y use Prim2DBase.x or y properties.
          */
+        @dynamicLevelProperty(1, pi => Prim2DBase.positionProperty = pi, false, true)
         public get position(): Vector2 {
             return this._position;
         }
@@ -1400,25 +1428,25 @@
          * Use this property when you only want to change one component of the position property
          */
         public get x(): number {
-            if (!this.position) {
+            if (!this._position) {
                 return null;
             }
-            return this.position.x;
+            return this._position.x;
         }
 
         public set x(value: number) {
             if (!this._checkPositionChange()) {
                 return;
             }
-            if (!this.position) {
+            if (!this._position) {
                 this._position = Vector2.Zero();
             }
 
-            if (this.position.x === value) {
+            if (this._position.x === value) {
                 return;
             }
 
-            this.position.x = value;
+            this._position.x = value;
             this.markAsDirty("position");
         }
 
@@ -1427,25 +1455,25 @@
          * Use this property when you only want to change one component of the position property
          */
         public get y(): number {
-            if (!this.position) {
+            if (!this._position) {
                 return null;
             }
-            return this.position.y;
+            return this._position.y;
         }
 
         public set y(value: number) {
             if (!this._checkPositionChange()) {
                 return;
             }
-            if (!this.position) {
+            if (!this._position) {
                 this._position = Vector2.Zero();
             }
 
-            if (this.position.y === value) {
+            if (this._position.y === value) {
                 return;
             }
 
-            this.position.y = value;
+            this._position.y = value;
             this.markAsDirty("position");
         }
 
@@ -1564,7 +1592,7 @@
             return this._zOrder || (1 - this._hierarchyDepthOffset);
         }
 
-        @instanceLevelProperty(5, pi => Prim2DBase.originProperty = pi, false, true)
+        @dynamicLevelProperty(5, pi => Prim2DBase.originProperty = pi, false, true)
         public set origin(value: Vector2) {
             this._origin = value;
         }
@@ -1696,7 +1724,7 @@
         }
 
         private get _hasPadding(): boolean {
-            return (this._padding !== null) || (this._paddingAlignment !== null);
+            return this._padding !== null;
         }
 
         @dynamicLevelProperty(11, pi => Prim2DBase.marginAlignmentProperty = pi)
@@ -1710,18 +1738,6 @@
             return this._marginAlignment;
         }
 
-
-        @dynamicLevelProperty(12, pi => Prim2DBase.paddingAlignmentProperty = pi)
-        /**
-         * You can get/set the vertical alignment through this property
-         */
-        public get paddingAlignment(): PrimitiveAlignment {
-            if (!this._paddingAlignment) {
-                this._paddingAlignment = new PrimitiveAlignment(() => this._positioningDirty());
-            }
-            return this._paddingAlignment;
-        }
-
         public get layoutEngine(): LayoutEngineBase {
             if (!this._layoutEngine) {
                 this._layoutEngine = new CanvasLayoutEngine();
@@ -1806,7 +1822,7 @@
 
         /**
          * Determine if the size is automatically computed or fixed because manually specified.
-         * Use getActualSize() to get the final/real size of the primitive
+         * Use the actualSize property to get the final/real size of the primitive
          * @returns true if the size is automatically computed, false if it were manually specified.
          */
         public get isSizeAuto(): boolean {
@@ -1815,11 +1831,11 @@
 
         /**
          * Determine if the position is automatically computed or fixed because manually specified.
-         * Use getActualPosition() to get the final/real position of the primitive
+         * Use the actualPosition property to get the final/real position of the primitive
          * @returns true if the position is automatically computed, false if it were manually specified.
          */
         public get isPositionAuto(): boolean {
-            return this.position == null;
+            return this._position == null;
         }
 
         /**
@@ -1829,6 +1845,19 @@
             return this._pointerEventObservable;
         }
 
+        public findById(id: string): Prim2DBase {
+            if (this._id === id) {
+                return this;
+            }
+
+            for (let child of this._children) {
+                let r = child.findById(id);
+                if (r != null) {
+                    return r;
+                }
+            }
+        }
+
         protected onZOrderChanged() {
             
         }
@@ -2054,7 +2083,15 @@
                 return;
             }
 
-            this.parent._setFlags(SmartPropertyPrim.flagLayoutDirty);
+            this._parent._setLayoutDirty();
+        }
+
+        protected _setLayoutDirty() {
+            if (!this.isDirty) {
+                this.onPrimBecomesDirty();
+            }
+            this._setFlags(SmartPropertyPrim.flagLayoutDirty);
+
         }
 
         private _checkPositionChange(): boolean {
@@ -2076,13 +2113,48 @@
             
         }
 
+        private static _t0: Matrix = new Matrix();
+        private static _t1: Matrix = new Matrix();
+        private static _t2: Matrix = new Matrix();
+        private static _v0: Vector2 = Vector2.Zero();   // Must stay with the value 0,0
+
         private _updateLocalTransform(): boolean {
-            let tflags = Prim2DBase.positionProperty.flagId | Prim2DBase.rotationProperty.flagId | Prim2DBase.scaleProperty.flagId;
-            if (this.checkPropertiesDirty(tflags)) {
+            let parentMarginOffsetChanged = false;
+            let parentMarginOffset: Vector2 = null;
+            if (this._parent) {
+                parentMarginOffset = this._parent._margingOffset;
+                parentMarginOffsetChanged = !parentMarginOffset.equals(this._parentMargingOffset);
+                this._parentMargingOffset.copyFrom(parentMarginOffset);
+            } else {
+                parentMarginOffset = Prim2DBase._v0;
+            }
+
+            let tflags = Prim2DBase.actualPositionProperty.flagId | Prim2DBase.rotationProperty.flagId | Prim2DBase.scaleProperty.flagId | Prim2DBase.originProperty.flagId;
+            if (parentMarginOffsetChanged || this.checkPropertiesDirty(tflags)) {
                 var rot = Quaternion.RotationAxis(new Vector3(0, 0, 1), this._rotation);
-                var local = Matrix.Compose(new Vector3(this._scale, this._scale, this._scale), rot, new Vector3(this._position.x, this._position.y, 0));
+                var local: Matrix;
+
+                if (this._origin.x === 0 && this._origin.y === 0) {
+                    local = Matrix.Compose(new Vector3(this._scale, this._scale, 1), rot, new Vector3(this.actualPosition.x + parentMarginOffset.x, this.actualPosition.y + parentMarginOffset.y, 0));
+                    this._localTransform = local;
+                } else {
+                    // -Origin offset
+                    let as = this.actualSize;
+                    Matrix.TranslationToRef((-as.width * this._origin.x), (-as.height * this._origin.y), 0, Prim2DBase._t0);
+
+                    // -Origin * rotation
+                    rot.toRotationMatrix(Prim2DBase._t1);
+                    Prim2DBase._t0.multiplyToRef(Prim2DBase._t1, Prim2DBase._t2);
+
+                    // -Origin * rotation * scale
+                    Matrix.ScalingToRef(this._scale, this._scale, 1, Prim2DBase._t0);
+                    Prim2DBase._t2.multiplyToRef(Prim2DBase._t0, Prim2DBase._t1);
+
+                    // -Origin * rotation * scale * (Origin + Position + Parent Margin Offset)
+                    Matrix.TranslationToRef((as.width * this._origin.x) + this.actualPosition.x + parentMarginOffset.x, (as.height * this._origin.y) + this.actualPosition.y + parentMarginOffset.y, 0, Prim2DBase._t2);
+                    Prim2DBase._t1.multiplyToRef(Prim2DBase._t2, this._localTransform);
+                }
 
-                this._localTransform = local;
                 this.clearPropertiesDirty(tflags);
                 return true;
             }
@@ -2101,7 +2173,8 @@
 
             // Update actualSize only if there' not positioning to recompute and the size changed
             // Otherwise positioning will take care of it.
-            if (!this._isFlagSet(SmartPropertyPrim.flagLayoutDirty) && this.checkPropertiesDirty(Prim2DBase.sizeProperty.flagId)) {
+            let sizeDirty = this.checkPropertiesDirty(Prim2DBase.sizeProperty.flagId);
+            if (!this._isFlagSet(SmartPropertyPrim.flagLayoutDirty) && sizeDirty) {
                 if (this.size.width != null) {
                     this.actualSize.width = this.size.width;
                 }
@@ -2112,21 +2185,25 @@
             }
 
             // Check for layout update
+            let positioningDirty = this._isFlagSet(SmartPropertyPrim.flagPositioningDirty);
             if (this._isFlagSet(SmartPropertyPrim.flagLayoutDirty)) {
                 this._layoutEngine.updateLayout(this);
 
                 this._clearFlags(SmartPropertyPrim.flagLayoutDirty);
             }
 
+            let positioningComputed = positioningDirty && !this._isFlagSet(SmartPropertyPrim.flagPositioningDirty);
+
             // Check for positioning update
-            if (this._isFlagSet(SmartPropertyPrim.flagPositioningDirty) || (this._parent && !this._parent.contentArea.equals(this._parentContentArea))) {
+            if (!positioningComputed && (sizeDirty || this._isFlagSet(SmartPropertyPrim.flagPositioningDirty) || (this._parent && !this._parent.contentArea.equals(this._parentContentArea)))) {
                 this._updatePositioning();
 
                 this._clearFlags(SmartPropertyPrim.flagPositioningDirty);
+                positioningComputed = true;
+            }
 
-                if (this._parent) {
-                    this._parentContentArea.copyFrom(this._parent.contentArea);
-                }
+            if (positioningComputed && this._parent) {
+                this._parentContentArea.copyFrom(this._parent.contentArea);
             }
 
             // Check if we must update this prim
@@ -2141,35 +2218,16 @@
                 let localDirty = this._updateLocalTransform();
 
                 // Check if there are changes in the parent that will force us to update the global matrix
-                let parentDirty = false;
-                let parentMarginOffsetChanged = false;
-                let parentMarginOffset: Vector2 = null;
-                if (this._parent) {
-                    parentMarginOffset = this._parent._margingOffset;
-                    parentDirty = this._parent._globalTransformStep !== this._parentTransformStep;
-                    parentMarginOffsetChanged = !parentMarginOffset.equals(this._parentMargingOffset);
-                }
+                let parentDirty = (this._parent!=null) ? (this._parent._globalTransformStep !== this._parentTransformStep) : false;
 
                 // Check if we have to update the globalTransform
-                if (!this._globalTransform || localDirty || parentDirty || parentMarginOffsetChanged) {
+                if (!this._globalTransform || localDirty || parentDirty) {
                     let globalTransform = this._parent ? this._parent._globalTransform : null;
-                    if (parentMarginOffset && (parentMarginOffset.x !== 0 || parentMarginOffset.y !== 0)) {
-                        globalTransform = globalTransform.clone();
-                        // parentMarginOffset is expressed in bottom/left, so to apply origin on it (which we have to), we offset the by -0.5 to put it in the same frame of reference
-                        globalTransform.m[12] += parentMarginOffset.x * (this._origin.x - 0.5); 
-                        globalTransform.m[13] += parentMarginOffset.y * (this._origin.y - 0.5);
-                    }
-
                     this._globalTransform = this._parent ? this._localTransform.multiply(globalTransform) : this._localTransform;
                     this._invGlobalTransform = Matrix.Invert(this._globalTransform);
 
                     this._globalTransformStep = this.owner._globalTransformProcessStep + 1;
                     this._parentTransformStep = this._parent ? this._parent._globalTransformStep : 0;
-
-                    if (parentMarginOffsetChanged) {
-                        this._parentMargingOffset.x = parentMarginOffset.x;
-                        this._parentMargingOffset.y = parentMarginOffset.y;
-                    }
                 }
                 this._globalTransformProcessStep = this.owner._globalTransformProcessStep;
             }
@@ -2181,63 +2239,60 @@
             }
         }
 
-        private static _icPos           = Vector2.Zero();
-        private static _icArea          = Size.Zero();
-        private static _newContent      = Size.Zero();
+        private static _icPos  = Vector2.Zero();
+        private static _icArea = Size.Zero();
+        private static _size   = Size.Zero();
 
         private _updatePositioning() {
             // From this point we assume that the primitive layoutArea is computed and up to date.
             // We know have to :
-            //  1. Determine the PaddingArea based on the padding property, which will set the size property of the primitive
-            //  2. Determine the contentArea based on the primitive's initialContentArea and the margin property.
+            //  1. Determine the PaddingArea and the ActualPosition based on the margin/marginAlignment properties, which will also set the size property of the primitive
+            //  2. Determine the contentArea based on the padding property.
 
             // Auto Create PaddingArea if there's no actualSize on width&|height to allocate the whole content available to the paddingArea where the actualSize is null
-            if (!this._hasPadding && (this.actualSize.width == null || this.actualSize.height == null)) {
+            if (!this._hasMargin && (this.actualSize.width == null || this.actualSize.height == null)) {
                 if (this.actualSize.width == null) {
-                    this.paddingAlignment.horizontal = PrimitiveAlignment.AlignStretch;
+                    this.marginAlignment.horizontal = PrimitiveAlignment.AlignStretch;
                 }
 
                 if (this.actualSize.height == null) {
-                    this.paddingAlignment.vertical = PrimitiveAlignment.AlignStretch;
+                    this.marginAlignment.vertical = PrimitiveAlignment.AlignStretch;
                 }
             }
 
             // Compute the PaddingArea
-            if (this._hasPadding) {
-                this.padding.compute(this._layoutArea, this.size, this.paddingAlignment, this._paddingOffset, this._paddingArea);
-
-                this.position = this._paddingOffset.clone();
+            if (this._hasMargin) {
+                this.margin.computeWithAlignment(this._layoutArea, this.size, this.marginAlignment, this._paddingOffset, Prim2DBase._size);
 
-                // Origin correction
-                if (this.origin.x !== 0 || this.origin.y !== 0) {
-                    this.position.x += this._paddingArea.width  * this.origin.x;
-                    this.position.y += this._paddingArea.height * this.origin.y;
-                }
+                this.actualPosition = this._paddingOffset.clone();
 
                 if (this.size.width != null) {
-                    this.size.width = this._paddingArea.width;
+                    this.size.width = Prim2DBase._size.width;
                 }
                 if (this.size.height != null) {
-                    this.size.height = this._paddingArea.height;
+                    this.size.height = Prim2DBase._size.height;
                 }
-                this.actualSize = this._paddingArea.clone();
+                this.actualSize.copyFrom(Prim2DBase._size.clone());
             }
 
             // No Padding property, the padding area is the same as the actualSize
             else {
                 this._paddingOffset.x    = 0;
                 this._paddingOffset.y    = 0;
-                this._paddingArea.copyFrom(this.actualSize);
             }
 
-            if (this._hasMargin) {
-                this._getInitialContentAreaToRef(this._paddingArea, Prim2DBase._icPos, Prim2DBase._icArea);
-                this.margin.compute(Prim2DBase._icArea, this._contentArea, this.marginAlignment, this._margingOffset, Prim2DBase._newContent);
+            if (this._hasPadding) {
+                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.margin.compute(Prim2DBase._icArea, this._margingOffset, Prim2DBase._size);
                 this._margingOffset.x += Prim2DBase._icPos.x;
                 this._margingOffset.y += Prim2DBase._icPos.y;
-                this._contentArea.copyFrom(Prim2DBase._newContent);              
+                this._contentArea.copyFrom(Prim2DBase._size);              
             } else {
-                this._getInitialContentAreaToRef(this._paddingArea, Prim2DBase._icPos, Prim2DBase._icArea);
+                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._margingOffset.copyFrom(Prim2DBase._icPos);
                 this._contentArea.copyFrom(Prim2DBase._icArea);
             }
@@ -2268,6 +2323,7 @@
 
             if (this._parent) {
                 this._renderGroup = <Group2D>this.parent.traverseUp(p => p instanceof Group2D && p.isRenderableGroup);
+                this._parentLayoutDirty();
             }
 
             // Make sure the prim is in the dirtyList if it should be
@@ -2317,10 +2373,10 @@
         private   _margin                : PrimitiveThickness;
         private   _padding               : PrimitiveThickness;
         private   _marginAlignment       : PrimitiveAlignment;
-        private   _paddingAlignment      : PrimitiveAlignment;
         public    _pointerEventObservable: Observable<PrimitivePointerInfo>;
         private   _id                    : string;
         private   _position              : Vector2;
+        private   _actualPosition        : Vector2;
         protected _size                  : Size;
         protected _actualSize            : Size;
         protected _minSize               : Size;
@@ -2328,7 +2384,6 @@
         protected _desiredSize           : Size;
         private   _layoutEngine          : LayoutEngineBase;
         private   _paddingOffset         : Vector2;
-        private   _paddingArea           : Size;
         private   _margingOffset         : Vector2;
         private   _parentMargingOffset   : Vector2;
         private   _parentContentArea     : Size;

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

@@ -227,10 +227,9 @@
             // If it's the case for one, check if the mouse is located in the quarter that we care about (the one who is visible) then finally make a distance check with the roundRadius radius to see if it's inside the circle quarter or outside.
 
             // First let remove the origin out the equation, to have the rectangle with an origin at bottom/left
-            let o = this.origin;
             let size = this.size;
-            Rectangle2D._i0.x = intersectInfo._localPickPosition.x + (size.width * o.x);
-            Rectangle2D._i0.y = intersectInfo._localPickPosition.y + (size.height * o.y);
+            Rectangle2D._i0.x = intersectInfo._localPickPosition.x;
+            Rectangle2D._i0.y = intersectInfo._localPickPosition.y;
 
             let rr = this.roundRadius;
             let rrs = rr * rr;
@@ -290,7 +289,7 @@
         }
 
         protected updateLevelBoundingInfo() {
-            BoundingInfo2D.CreateFromSizeToRef(this.size, this._levelBoundingInfo, this.origin);
+            BoundingInfo2D.CreateFromSizeToRef(this.size, 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) {
@@ -324,13 +323,15 @@
             position         ?: Vector2,
             x                ?: number,
             y                ?: number,
+            rotation         ?: number,
+            scale            ?: number,
             origin           ?: Vector2,
             size             ?: Size,
             width            ?: number,
             height           ?: number,
             roundRadius      ?: number,
-            fill             ?: IBrush2D,
-            border           ?: IBrush2D,
+            fill             ?: IBrush2D | string,
+            border           ?: IBrush2D | string,
             borderThickness  ?: number,
             isVisible        ?: boolean,
             marginTop        ?: number | string,
@@ -348,7 +349,6 @@
             padding          ?: string,
             paddingHAlignment?: number,
             paddingVAlignment?: number,
-            paddingAlignment ?: string,
         }) {
 
             // Avoid checking every time if the object exists
@@ -358,15 +358,11 @@
 
             super(settings);
 
-            let pos             = settings.position || new Vector2(settings.x || 0, settings.y || 0);
             let size            = settings.size || (new Size((settings.width === null) ? null : (settings.width || 10), (settings.height === null) ? null : (settings.height || 10)));
-            let fill            = settings.fill === undefined ? Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF") : settings.fill;
             let roundRadius     = (settings.roundRadius == null) ? 0 : settings.roundRadius;
             let borderThickness = (settings.borderThickness == null) ? 1 : settings.borderThickness;
 
-            this.position        = pos;
             this.size            = size;
-            this.fill            = fill;
             this.roundRadius     = roundRadius;
             this.borderThickness = borderThickness;
         }
@@ -464,8 +460,8 @@
             } else {
                 let rr = Math.round((this.roundRadius - (this.roundRadius/Math.sqrt(2))) * 1.3);
                 initialContentPosition.x = initialContentPosition.y = rr;
-                initialContentArea.width = primSize.width - (rr * 2);
-                initialContentArea.height = primSize.height - (rr * 2);
+                initialContentArea.width = Math.max(0, primSize.width - (rr * 2));
+                initialContentArea.height = Math.max(0, primSize.height - (rr * 2));
             }
         }
 

+ 5 - 13
src/Canvas2d/babylon.renderablePrim2d.ts

@@ -272,11 +272,6 @@
             return null;
         }
 
-        @instanceData()
-        get origin(): Vector2 {
-            return null;
-        }
-
         getClassTreeInfo(): ClassTreeInfo<InstanceClassInfo, InstancePropInfo> {
             if (!this.typeInfo) {
                 this.typeInfo = ClassTreeInfo.get<InstanceClassInfo, InstancePropInfo>(Object.getPrototypeOf(this));
@@ -356,7 +351,6 @@
         constructor(settings?: {
             parent       ?: Prim2DBase, 
             id           ?: string,
-            position     ?: Vector2,
             origin       ?: Vector2,
             isVisible    ?: boolean,
         }) {
@@ -775,10 +769,9 @@
         /**
          * Update the instanceDataBase level properties of a part
          * @param part the part to update
-         * @param positionOffset to use in multi part per primitive (e.g. the Text2D has N parts for N letter to display), this give the offset to apply (e.g. the position of the letter from the bottom/left corner of the text). You MUST also set customSize.
-         * @param customSize to use in multi part per primitive, this is the size of the overall primitive to display (the bounding rect's size of the Text, for instance). This is mandatory to compute correct transformation based on the Primitive's origin property.
+         * @param positionOffset to use in multi part per primitive (e.g. the Text2D has N parts for N letter to display), this give the offset to apply (e.g. the position of the letter from the bottom/left corner of the text).
          */
-        protected updateInstanceDataPart(part: InstanceDataBase, positionOffset: Vector2 = null, customSize: Size = null) {
+        protected updateInstanceDataPart(part: InstanceDataBase, positionOffset: Vector2 = null) {
             let t = this._globalTransform.multiply(this.renderGroup.invGlobalTransform);
             let size = (<Size>this.renderGroup.viewportSize);
             let zBias = this.actualZOffset;
@@ -786,9 +779,9 @@
             let offX = 0;
             let offY = 0;
             // If there's an offset, apply the global transformation matrix on it to get a global offset
-            if (positionOffset && customSize) {
-                offX = (positionOffset.x-(customSize.width*this.origin.x)) * t.m[0] + (positionOffset.y-(customSize.height*this.origin.y)) * t.m[4];
-                offY = (positionOffset.x-(customSize.width*this.origin.x)) * t.m[1] + (positionOffset.y-(customSize.height*this.origin.y)) * t.m[5];
+            if (positionOffset) {
+                offX = positionOffset.x * t.m[0] + positionOffset.y * t.m[4];
+                offY = positionOffset.x * t.m[1] + positionOffset.y * t.m[5];
             }
 
             // Have to convert the coordinates to clip space which is ranged between [-1;1] on X and Y axis, with 0,0 being the left/bottom corner
@@ -803,7 +796,6 @@
             let ty = new Vector4(t.m[1] * 2 / h, t.m[5] * 2 / h, 0/*t.m[9]*/, ((t.m[13] + offY) * 2 / h) - 1);
             part.transformX = tx;
             part.transformY = ty;
-            part.origin = this.origin;
 
             // Stores zBias and it's inverse value because that's needed to compute the clip space W coordinate (which is 1/Z, so 1/zBias)
             part.zBias = new Vector2(zBias, invZBias);

+ 22 - 4
src/Canvas2d/babylon.shape2d.ts

@@ -45,8 +45,8 @@
         }
 
         constructor(settings?: {
-            fill           ?: IBrush2D,
-            border         ?: IBrush2D,
+            fill           ?: IBrush2D | string,
+            border         ?: IBrush2D | string,
             borderThickness?: number,
         }) {
 
@@ -56,8 +56,26 @@
                 settings = {};
             }
 
-            this.border = settings.border;
-            this.fill = settings.fill;
+            let borderBrush: IBrush2D = null;
+            if (settings.border) {
+                if (typeof (settings.border) === "string") {
+                    borderBrush = Canvas2D.GetBrushFromString(<string>settings.border);
+                } else {
+                    borderBrush = <IBrush2D>settings.border;
+                }
+            }
+
+            let fillBrush: IBrush2D = null;
+            if (settings.fill) {
+                if (typeof (settings.fill) === "string") {
+                    fillBrush = Canvas2D.GetBrushFromString(<string>settings.fill);
+                } else {
+                    fillBrush = <IBrush2D>settings.fill;
+                }
+            }
+
+            this.border = borderBrush;
+            this.fill = fillBrush;
             this.borderThickness = settings.borderThickness;
         }
 

+ 14 - 15
src/Canvas2d/babylon.smartPropertyPrim.ts

@@ -348,29 +348,28 @@
                 this.handleGroupChanged(propInfo);
             }
 
-            // Check if we need to dirty only if the type change and make the test
-            var skipDirty = false;
+            // 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) {
                 var cvProto = (<any>curValue).__proto__;
                 var nvProto = (<any>newValue).__proto__;
 
-                skipDirty = (cvProto === nvProto);
+                instanceDirty = (cvProto === nvProto);
             }
 
             // Set the dirty flags
-            if (!skipDirty) {
-                if (propInfo.kind === Prim2DPropInfo.PROPKIND_MODEL) {
-                    if (!this.isDirty) {
-                        this.onPrimBecomesDirty();
-                    }
-                    this._setFlags(SmartPropertyPrim.flagModelDirty);
-                } else if ((propInfo.kind === Prim2DPropInfo.PROPKIND_INSTANCE) || (propInfo.kind === Prim2DPropInfo.PROPKIND_DYNAMIC)) {
-                    if (!this.isDirty) {
-                        this.onPrimBecomesDirty();
-                    }
-                    this._instanceDirtyFlags |= propMask;
-                }
+            if (!instanceDirty && (propInfo.kind === Prim2DPropInfo.PROPKIND_MODEL)) {
+                this.onPrimitivePropertyDirty(SmartPropertyPrim.flagModelDirty);
+            } else if (instanceDirty || (propInfo.kind === Prim2DPropInfo.PROPKIND_INSTANCE) || (propInfo.kind === Prim2DPropInfo.PROPKIND_DYNAMIC)) {
+                this.onPrimitivePropertyDirty(propMask);
+            }
+        }
+
+        protected onPrimitivePropertyDirty(propFlagId: number) {
+            if (!this.isDirty) {
+                this.onPrimBecomesDirty();
             }
+            this._instanceDirtyFlags |= propFlagId;
         }
 
         protected handleGroupChanged(prop: Prim2DPropInfo) {

+ 1 - 20
src/Canvas2d/babylon.sprite2d.ts

@@ -184,7 +184,7 @@
         }
 
         protected updateLevelBoundingInfo() {
-            BoundingInfo2D.CreateFromSizeToRef(this.size, this._levelBoundingInfo, this.origin);
+            BoundingInfo2D.CreateFromSizeToRef(this.size, this._levelBoundingInfo);
         }
 
         public getAnimatables(): IAnimatable[] {
@@ -201,24 +201,6 @@
             return true;
         }
 
-        //protected setupSprite2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, texture: Texture, spriteSize: Size, spriteLocation: Vector2, invertY: boolean, alignToPixel: boolean, isVisible: boolean, marginTop: number | string, marginLeft: number | string, marginRight: number | string, marginBottom: number | string, vAlignment: number, hAlignment: number) {
-        //    this.setupRenderablePrim2D(owner, parent, id, position, origin, isVisible, marginTop, marginLeft, marginRight, marginBottom, hAlignment, vAlignment);
-        //    this.texture = texture;
-        //    this.texture.wrapU = Texture.CLAMP_ADDRESSMODE;
-        //    this.texture.wrapV = Texture.CLAMP_ADDRESSMODE;
-        //    this.size = spriteSize || null;
-        //    this.spriteLocation = spriteLocation || new Vector2(0,0);
-        //    this.spriteFrame = 0;
-        //    this.invertY = invertY;
-        //    this.alignToPixel = alignToPixel;
-        //    this._isTransparent = true;
-
-        //    if (!this.size) {
-        //        var s = texture.getSize();
-        //        this.size = new Size(s.width, s.height);
-        //    }
-        //}
-
         /**
          * Create an 2D Sprite primitive
          * @param parent the parent primitive, must be a valid primitive (or the Canvas)
@@ -265,7 +247,6 @@
             padding          ?: string,
             paddingHAlignment?: number,
             paddingVAlignment?: number,
-            paddingAlignment ?: string,
         }) {
 
             if (!settings) {

+ 12 - 18
src/Canvas2d/babylon.text2d.ts

@@ -151,7 +151,11 @@
         public set text(value: string) {
             this._text = value;
             this._textSize = null;    // A change of text will reset the TextSize which will be recomputed next time it's used
+            this._size = null;
             this._updateCharCount();
+
+            // Trigger a textSize to for a sizeChange if necessary, which is needed for layout to recompute
+            let s = this.textSize;
         }
 
         @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 4, pi => Text2D.sizeProperty = pi)
@@ -170,13 +174,16 @@
             if (this._actualSize) {
                 return this._actualSize;
             }
-
             return this.size;
         }
 
         public get textSize(): Size {
-            if (!this._textSize) {
-                this._textSize = this.fontTexture.measureText(this._text, this._tabulationSize);
+            if (!this._textSize && this.owner) {
+                let newSize = this.fontTexture.measureText(this._text, this._tabulationSize);
+                if (newSize !== this._textSize) {
+                    this.onPrimitivePropertyDirty(Prim2DBase.sizeProperty.flagId);
+                }
+                this._textSize = newSize;
             }
             return this._textSize;
         }
@@ -204,20 +211,9 @@
         }
 
         protected updateLevelBoundingInfo() {
-            BoundingInfo2D.CreateFromSizeToRef(this.actualSize, this._levelBoundingInfo, this.origin);
+            BoundingInfo2D.CreateFromSizeToRef(this.actualSize, this._levelBoundingInfo);
         }
 
-        //protected setupText2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, fontName: string, text: string, areaSize: Size, defaultFontColor: Color4, tabulationSize: number, isVisible: boolean, marginTop: number | string, marginLeft: number | string, marginRight: number | string, marginBottom: number | string, vAlignment: number, hAlignment: number) {
-        //    this.setupRenderablePrim2D(owner, parent, id, position, origin, isVisible, marginTop, marginLeft, marginRight, marginBottom, hAlignment, vAlignment);
-
-        //    this.fontName = fontName;
-        //    this.defaultFontColor = defaultFontColor;
-        //    this.text = text;
-        //    this.size = areaSize;
-        //    this._tabulationSize = tabulationSize;
-        //    this.isAlphaTest = true;
-        //}
-
         /**
          * Create a Text primitive
          * @param parent the parent primitive, must be a valid primitive (or the Canvas)
@@ -264,7 +260,6 @@
             padding          ?: string,
             paddingHAlignment?: number,
             paddingVAlignment?: number,
-            paddingAlignment ?: string,
         }) {
 
             if (!settings) {
@@ -357,7 +352,6 @@
                 let d = <Text2DInstanceData>part;
                 let texture = this.fontTexture;
                 let ts = texture.getSize();
-                let textSize = texture.measureText(this.text, this._tabulationSize);
                 let offset = Vector2.Zero();
                 let charxpos = 0;
                 d.dataElementCount = this._charCount;
@@ -384,7 +378,7 @@
                         continue;
                     }
 
-                    this.updateInstanceDataPart(d, offset, textSize);
+                    this.updateInstanceDataPart(d, offset);
 
                     let ci = texture.getChar(char);
                     offset.x += ci.charWidth;

+ 1 - 2
src/Shaders/ellipse2d.vertex.fx

@@ -9,7 +9,6 @@ attribute float index;
 att vec2 zBias;
 att vec4 transformX;
 att vec4 transformY;
-att vec2 origin;
 
 #ifdef Border
 att float borderThickness;
@@ -100,7 +99,7 @@ void main(void) {
 #endif
 
 	vec4 pos;
-	pos.xy = (pos2.xy - origin) * properties.xy;
+	pos.xy = pos2.xy * properties.xy;
 	pos.z = 1.0;
 	pos.w = 1.0;
 	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, 1);

+ 3 - 4
src/Shaders/lines2d.vertex.fx

@@ -9,9 +9,6 @@ attribute vec2 position;
 att vec2 zBias;
 att vec4 transformX;
 att vec4 transformY;
-att vec2 origin;
-att vec2 boundingMin;
-att vec2 boundingMax;
 
 #ifdef FillSolid
 att vec4 fillSolidColor;
@@ -22,6 +19,8 @@ att vec4 borderSolidColor;
 #endif
 
 #ifdef FillGradient
+att vec2 boundingMin;
+att vec2 boundingMax;
 att vec4 fillGradientColor1;
 att vec4 fillGradientColor2;
 att vec4 fillGradientTY;
@@ -60,7 +59,7 @@ void main(void) {
 #endif
 
 	vec4 pos;
-	pos.xy = position.xy - (origin.xy * (boundingMax - boundingMin));
+	pos.xy = position.xy;
 	pos.z = 1.0;
 	pos.w = 1.0;
 	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, 1);

+ 1 - 2
src/Shaders/rect2d.vertex.fx

@@ -9,7 +9,6 @@ attribute float index;
 att vec2 zBias;
 att vec4 transformX;
 att vec4 transformY;
-att vec2 origin;
 
 #ifdef Border
 att float borderThickness;
@@ -198,7 +197,7 @@ void main(void) {
 #endif
 
 	vec4 pos;
-	pos.xy = (pos2.xy - origin) * properties.xy;
+	pos.xy = pos2.xy * properties.xy;
 	pos.z = 1.0;
 	pos.w = 1.0;
 	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, 1);

+ 2 - 3
src/Shaders/sprite2d.vertex.fx

@@ -10,7 +10,6 @@ attribute float index;
 
 att vec2 topLeftUV;
 att vec2 sizeUV;
-att vec2 origin;
 att vec2 textureSize;
 
 // x: frame, y: invertY, z: alignToPixel
@@ -68,9 +67,9 @@ void main(void) {
 	vec4 pos;
 	if (alignToPixel == 1.0)
 	{
-		pos.xy = floor((pos2.xy * sizeUV * textureSize) - origin);
+		pos.xy = floor(pos2.xy * sizeUV * textureSize);
 	} else {
-		pos.xy = (pos2.xy * sizeUV * textureSize) - origin;
+		pos.xy = pos2.xy * sizeUV * textureSize;
 	}
 
 	pos.z = 1.0;

+ 1 - 2
src/Shaders/text2d.vertex.fx

@@ -14,7 +14,6 @@ att vec4 transformY;
 
 att vec2 topLeftUV;
 att vec2 sizeUV;
-att vec2 origin;
 att vec2 textureSize;
 att vec4 color;
 
@@ -52,7 +51,7 @@ void main(void) {
 
 	vColor = color;
 	vec4 pos;
-	pos.xy = floor((pos2.xy * sizeUV * textureSize) - origin);	// Align on target pixel to avoid bad interpolation
+	pos.xy = floor(pos2.xy * sizeUV * textureSize);	// Align on target pixel to avoid bad interpolation
 	pos.z = 1.0;
 	pos.w = 1.0;
 	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, 1);