Jelajahi Sumber

Canvas2D: bug fixes

 - fix pointerEventObservable not firing correctly when primitives are overlapping
 - fix autoSize computation
nockawa 8 tahun lalu
induk
melakukan
e4bd38e102

+ 49 - 0
canvas2D/src/Engine/babylon.bounding2d.ts

@@ -155,6 +155,16 @@
         }
 
         /**
+         * return the min/max extend of the bounding info.
+         * x, y, z, w are left, bottom, right and top
+         */
+        public minMax(): Vector4 {
+            let r = Vector4.Zero();
+            this.minMaxToRef(r);
+            return r;
+        }
+
+        /**
          * Update a vector2 with the max extend of the bounding info
          * @param result must be a valid/allocated vector2 that will contain the result of the operation
          */
@@ -164,6 +174,45 @@
         }
 
         /**
+         * Update a vector4 with the min/max extend of the bounding info
+         * x, y, z, w are left, bottom, right and top
+         * @param result must be a valid/allocated vector4 that will contain the result of the operation
+         */
+        public minMaxToRef(result: Vector4) {
+            result.x = this.center.x - this.extent.x;
+            result.y = this.center.y - this.extent.y;
+            result.z = this.center.x + this.extent.x;
+            result.w = this.center.y + this.extent.y;
+        }
+
+        /**
+         * Return the size of the boundingInfo rect surface
+         */
+        public size(): Size {
+            let r = Size.Zero();
+            this.sizeToRef(r);
+            return r;
+        }
+
+        /**
+         * Stores in the result object the size of the boundingInfo rect surface
+         * @param result
+         */
+        public sizeToRef(result: Size) {
+            result.width  = this.extent.x * 2;
+            result.height = this.extent.y * 2;
+        }
+
+        /**
+         * Inflate the boundingInfo with the given vector
+         * @param offset the extent will be incremented with offset and the radius will be computed again
+         */
+        public inflate(offset: Vector2) {
+            this.extent.addInPlace(offset);
+            this.radius = this.extent.length();
+        }
+
+        /**
          * Apply a transformation matrix to this BoundingInfo2D and return a new instance containing the result
          * @param matrix the transformation matrix to apply
          * @return the new instance containing the result of the transformation applied on this BoundingInfo2D

+ 56 - 74
canvas2D/src/Engine/babylon.canvas2d.ts

@@ -575,6 +575,9 @@
             } else {
                 // The pointer is inside the Canvas, do an intersection test
                 this.intersect(ii);
+
+                // Sort primitives to get them from top to bottom
+                ii.intersectedPrimitives = ii.intersectedPrimitives.sort((a, b) => a.prim.actualZOffset - b.prim.actualZOffset);
             }
 
             {
@@ -608,16 +611,31 @@
                 // Detect if the current pointer is captured, only fire event if they belong to the capture primitive
                 let capturedPrim = this.getCapturedPrimitive(this._primPointerInfo.pointerId);
 
-                // Notify the previous "over" prim that the pointer is no longer over it
-                if ((capturedPrim && capturedPrim === prevPrim) || (!capturedPrim && prevPrim && !prevPrim.isDisposed)) {
-                    this._primPointerInfo.updateRelatedTarget(prevPrim, this._previousOverPrimitive.intersectionLocation);
-                    this._bubbleNotifyPrimPointerObserver(prevPrim, PrimitivePointerInfo.PointerOut, null);
-                }
+                // See the NOTE section of: https://www.w3.org/TR/pointerevents/#setting-pointer-capture
+                if (capturedPrim) {
+                    if (capturedPrim === prevPrim) {
+                        this._primPointerInfo.updateRelatedTarget(prevPrim, this._previousOverPrimitive.intersectionLocation);
+                        this._bubbleNotifyPrimPointerObserver(prevPrim, PrimitivePointerInfo.PointerOut, null);
+                    } else if (capturedPrim === actualPrim) {
+                        this._primPointerInfo.updateRelatedTarget(actualPrim, this._actualOverPrimitive.intersectionLocation);
+                        this._bubbleNotifyPrimPointerObserver(actualPrim, PrimitivePointerInfo.PointerOver, null);
+                    }
+                } else {
+                    // Check for Out & Leave
+                    for (let prev of this._previousIntersectionList) {
+                        if (!Tools.first(this._actualIntersectionList, (pii) => pii.prim === prev.prim)) {
+                            this._primPointerInfo.updateRelatedTarget(prev.prim, prev.intersectionLocation);
+                            this._bubbleNotifyPrimPointerObserver(prev.prim, PrimitivePointerInfo.PointerOut, null);
+                        }
+                    }
 
-                // Notify the new "over" prim that the pointer is over it
-                if ((capturedPrim && capturedPrim === actualPrim) || (!capturedPrim && actualPrim)) {
-                    this._primPointerInfo.updateRelatedTarget(actualPrim, this._actualOverPrimitive.intersectionLocation);
-                    this._bubbleNotifyPrimPointerObserver(actualPrim, PrimitivePointerInfo.PointerOver, null);
+                    // Check for Over & Enter
+                    for (let actual of this._actualIntersectionList) {
+                        if (!Tools.first(this._previousIntersectionList, (pii) => pii.prim === actual.prim)) {
+                            this._primPointerInfo.updateRelatedTarget(actual.prim, actual.intersectionLocation);
+                            this._bubbleNotifyPrimPointerObserver(actual.prim, PrimitivePointerInfo.PointerOver, null);
+                        }
+                    }
                 }
             }
 
@@ -649,7 +667,7 @@
             }
 
             let pii = this._primPointerInfo;
-            debug += `[RID:${this.scene.getRenderId()}] [${prim.hierarchyDepth}] event:${PrimitivePointerInfo.getEventTypeName(mask)}, id: ${prim.id} (${Tools.getClassName(prim)}), primPos: ${pii.primitivePointerPos.toString()}, canvasPos: ${pii.canvasPointerPos.toString()}`;
+            debug += `[RID:${this.scene.getRenderId()}] [${prim.hierarchyDepth}] event:${PrimitivePointerInfo.getEventTypeName(mask)}, id: ${prim.id} (${Tools.getClassName(prim)}), primPos: ${pii.primitivePointerPos.toString()}, canvasPos: ${pii.canvasPointerPos.toString()}, relatedTarget: ${pii.relatedTarget.id}`;
             console.log(debug);
         }
 
@@ -657,56 +675,40 @@
             let ppi = this._primPointerInfo;
             let event = eventData ? eventData.event : null;
 
-            // In case of PointerOver/Out we will first notify the parent with PointerEnter/Leave
-            if ((mask & (PrimitivePointerInfo.PointerOver | PrimitivePointerInfo.PointerOut)) !== 0) {
-                this._notifParents(prim, mask);
-            }
-
-            let bubbleCancelled = false;
             let cur = prim;
             while (cur && !cur.isDisposed) {
-                // Only trigger the observers if the primitive is intersected (except for out)
-                if (!bubbleCancelled) {
-                    this._updatePrimPointerPos(cur);
-
-                    // Exec the observers
-                    this._debugExecObserver(cur, mask);
-                    if (!cur._pointerEventObservable.notifyObservers(ppi, mask) && eventData instanceof PointerInfoPre) {
-                        eventData.skipOnPointerObservable = true;
-                        return false;
+                this._updatePrimPointerPos(cur);
+
+                // For the first level we have to fire Enter or Leave for corresponding Over or Out
+                if (cur === prim) {
+                    // Fire the proper notification
+                    if (mask === PrimitivePointerInfo.PointerOver) {
+                        this._debugExecObserver(prim, PrimitivePointerInfo.PointerEnter);
+                        prim._pointerEventObservable.notifyObservers(ppi, PrimitivePointerInfo.PointerEnter);
                     }
 
-                    this._triggerActionManager(cur, ppi, mask, event);
-
-                    // Bubble canceled? If we're not executing PointerOver or PointerOut, quit immediately
-                    // If it's PointerOver/Out we have to trigger PointerEnter/Leave no matter what
-                    if (ppi.cancelBubble) {
-                        if ((mask & (PrimitivePointerInfo.PointerOver | PrimitivePointerInfo.PointerOut)) === 0) {
-                            return false;
-                        }
-
-                        // We're dealing with PointerOver/Out, let's keep looping to fire PointerEnter/Leave, but not Over/Out anymore
-                        bubbleCancelled = true;
+                    // Trigger a PointerLeave corresponding to the PointerOut
+                    else if (mask === PrimitivePointerInfo.PointerOut) {
+                        this._debugExecObserver(prim, PrimitivePointerInfo.PointerLeave);
+                        prim._pointerEventObservable.notifyObservers(ppi, PrimitivePointerInfo.PointerLeave);
                     }
                 }
 
-                // If bubble is cancel we didn't update the Primitive Pointer Pos yet, let's do it
-                if (bubbleCancelled) {
-                    this._updatePrimPointerPos(cur);
+                // Exec the observers
+                this._debugExecObserver(cur, mask);
+                if (!cur._pointerEventObservable.notifyObservers(ppi, mask) && eventData instanceof PointerInfoPre) {
+                    eventData.skipOnPointerObservable = true;
+                    return false;
                 }
 
-                // NOTE TO MYSELF, this is commented right now because it doesn't seemed needed but I can't figure out why I put this code in the first place
-                //// Trigger a PointerEnter corresponding to the PointerOver
-                //if (mask === PrimitivePointerInfo.PointerOver) {
-                //    this._debugExecObserver(cur, PrimitivePointerInfo.PointerEnter);
-                //    cur._pointerEventObservable.notifyObservers(ppi, PrimitivePointerInfo.PointerEnter);
-                //}
+                this._triggerActionManager(cur, ppi, mask, event);
 
-                //// Trigger a PointerLeave corresponding to the PointerOut
-                //else if (mask === PrimitivePointerInfo.PointerOut) {
-                //    this._debugExecObserver(cur, PrimitivePointerInfo.PointerLeave);
-                //    cur._pointerEventObservable.notifyObservers(ppi, PrimitivePointerInfo.PointerLeave);
-                //}
+                // Bubble canceled? If we're not executing PointerOver or PointerOut, quit immediately
+                // If it's PointerOver/Out we have to trigger PointerEnter/Leave no matter what
+                if (ppi.cancelBubble) {
+                    return false;
+
+                }
 
                 // Loop to the parent
                 cur = cur.parent;
@@ -808,29 +810,6 @@
             }
         }
 
-        _notifParents(prim: Prim2DBase, mask: number) {
-            let pii = this._primPointerInfo;
-
-            let curPrim: Prim2DBase = this;
-
-            while (curPrim) {
-                this._updatePrimPointerPos(curPrim);
-
-                // Fire the proper notification
-                if (mask === PrimitivePointerInfo.PointerOver) {
-                    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(curPrim, PrimitivePointerInfo.PointerLeave);
-                    curPrim._pointerEventObservable.notifyObservers(pii, PrimitivePointerInfo.PointerLeave);
-                }
-                curPrim = curPrim.parent;
-            }
-        }
-
         /**
          * Don't forget to call the dispose method when you're done with the Canvas instance.
          * But don't worry, if you dispose its scene, the canvas will be automatically disposed too.
@@ -1068,7 +1047,10 @@
          * Return 
          */
         public get overPrim(): Prim2DBase {
-            return this._actualOverPrimitive ? this._actualOverPrimitive.prim : null;
+            if (this._actualIntersectionList && this._actualIntersectionList.length>0) {
+                return this._actualIntersectionList[0].prim;
+            }
+            return null;
         }
 
         /**

+ 44 - 12
canvas2D/src/Engine/babylon.prim2dBase.ts

@@ -1511,7 +1511,7 @@
                     this.addChild(child);
 
                     // Good time to patch the hierarchy, it won't go very far if there's no need to
-                    if (this.owner != null) {
+                    if (this.owner != null && this._hierarchyDepth != null) {
                         child._patchHierarchy(this.owner);
                     }
                 }
@@ -1962,7 +1962,6 @@
                 Prim2DBase.boundinbBoxReentrency = false;
 
                 return this._boundingSize;
-
             }
             return this._size;
         }
@@ -2547,7 +2546,9 @@
             return this._localTransform;
         }
 
+        private static _bMinMax = Vector4.Zero();
         private static _bMax = Vector2.Zero();
+        private static _bSize = Size.Zero();
         private static _tpsBB = new BoundingInfo2D();
         /**
          * Get the boundingInfo associated to the primitive and its children.
@@ -2558,7 +2559,9 @@
                 if (this.owner) {
                     this.owner.boundingInfoRecomputeCounter.addCount(1, false);
                 }
-                if (this.isSizedByContent) {
+                let sizedByContent = this.isSizedByContent;
+
+                if (sizedByContent) {
                     this._boundingInfo.clear();
                 } else {
                     this._boundingInfo.copyFrom(this.levelBoundingInfo);
@@ -2572,10 +2575,28 @@
                     bi.unionToRef(tps, bi);
                 }
 
-                this._boundingInfo.maxToRef(Prim2DBase._bMax);
+                // If the size is determined by the content we have to update the contentArea
+                //  and compute the size considering the padding (if any)
+                if (sizedByContent) {
+                    bi.maxToRef(Prim2DBase._bMax);
+                    this._contentArea.width = Prim2DBase._bMax.x;
+                    this._contentArea.height = Prim2DBase._bMax.y;
+
+                    if (this._hasPadding) {
+                        let padding = this.padding;
+                        let mm = Prim2DBase._bMinMax;
+                        bi.minMaxToRef(mm);
+                        mm.z += padding.leftPixels + padding.rightPixels;
+                        mm.w += padding.bottomPixels + padding.topPixels;
+                        this._paddingOffset.copyFromFloats(padding.leftPixels, padding.bottomPixels, padding.rightPixels, padding.topPixels);
+                        BoundingInfo2D.CreateFromMinMaxToRef(mm.x, mm.z, mm.y, mm.w, bi);
+                    }
+                }
+
+                this._boundingInfo.sizeToRef(Prim2DBase._bSize);
                 this._boundingSize.copyFromFloats(
-                    (!this._size || this._size.width == null) ? Math.ceil(Prim2DBase._bMax.x) : this._size.width,
-                    (!this._size || this._size.height == null) ? Math.ceil(Prim2DBase._bMax.y) : this._size.height);
+                    (!this._size || this._size.width == null) ? Math.ceil(Prim2DBase._bSize.width) : this._size.width,
+                    (!this._size || this._size.height == null) ? Math.ceil(Prim2DBase._bSize.height) : this._size.height);
 
                 this._clearFlags(SmartPropertyPrim.flagBoundingInfoDirty);
             }
@@ -3482,12 +3503,6 @@
             if (this._hasPadding) {
                 // Two cases from here: the size of the Primitive is Auto, its content can't be shrink, so we resize the primitive itself
                 if (isSizeAuto) {
-                    let content = this.size.clone();
-                    this._getActualSizeFromContentToRef(content, Prim2DBase._icArea);
-                    this.padding.enlarge(Prim2DBase._icArea, this._paddingOffset, Prim2DBase._size);
-                    this._contentArea.copyFrom(content);
-                    this.actualSize = Prim2DBase._size.clone();
-
                     // Changing the padding has resize the prim, which forces us to recompute margin again
                     if (this._hasMargin) {
                         this.margin.computeWithAlignment(this.layoutArea, Prim2DBase._size, this.marginAlignment, this._marginOffset, Prim2DBase._size);
@@ -3533,6 +3548,23 @@
          * Children of this primitive will be positioned relative to the bottom/left corner of this area.
          */
         public get contentArea(): Size {
+            if (!this._size || this._size.width == null || this._size.height == null) {
+
+                if (Prim2DBase.boundinbBoxReentrency) {
+                    return Prim2DBase.nullSize;
+                }
+
+                if (!this._isFlagSet(SmartPropertyPrim.flagBoundingInfoDirty)) {
+                    return this._boundingSize;
+                }
+
+                Prim2DBase.boundinbBoxReentrency = true;
+                let b = this.boundingInfo;
+                Prim2DBase.boundinbBoxReentrency = false;
+
+                return this._contentArea;
+            } else
+
             // Check for positioning update
             if (this._isFlagSet(SmartPropertyPrim.flagPositioningDirty)) {
                 this._updatePositioning();