瀏覽代碼

Canvas2D: Animation and ActionManager support + bugs fixes

ActionEvent: adding a CreateNewFromPrimitive (which takes a prim as 'any' to allow building Core)

SmartPropertyPrim:
 - Adding the animation proeprty to support animations.
 - Adding Inline documentation

Prim2DBase:
 - Adding actionManager get property, supporting every trigger except KeyDown/Up and IntersectionEnter/Exit

Text2D: improved rendering quality and fix Origin computation

FontTexture: Now use NEAREST sampling mode to achieve a better rendering quality
nockawa 9 年之前
父節點
當前提交
1222b75340

+ 4 - 0
src/Actions/babylon.actionManager.ts

@@ -44,6 +44,10 @@
         public static CreateNewFromScene(scene: Scene, evt: Event): ActionEvent {
             return new ActionEvent(null, scene.pointerX, scene.pointerY, scene.meshUnderPointer, evt);
         }
+
+        public static CreateNewFromPrimitive(prim: any, pointerPos: Vector2, evt?: Event, additionalData?: any): ActionEvent {
+            return new ActionEvent(prim, pointerPos.x, pointerPos.y, null, evt, additionalData);
+        }
     }
 
     /**

+ 108 - 14
src/Canvas2d/babylon.canvas2d.ts

@@ -130,6 +130,7 @@
             this._hierarchySiblingZDelta = this._hierarchyLevelZFactor / this._hierarchyLevelMaxSiblingCount;
             this._primPointerInfo = new PrimitivePointerInfo();
             this._capturedPointers = new StringDictionary<Prim2DBase>();
+            this._pickStartingPosition = Vector2.Zero();
 
             this.setupGroup2D(this, null, name, Vector2.Zero(), size, this._cachingStrategy===Canvas2D.CACHESTRATEGY_ALLGROUPS ? Group2D.GROUPCACHEBEHAVIOR_DONTCACHEOVERRIDE : Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY);
 
@@ -208,7 +209,7 @@
             }
 
             this._primPointerInfo.updateRelatedTarget(primitive, Vector2.Zero());
-            this._bubbleNotifyPrimPointerObserver(primitive, PrimitivePointerInfo.PointerGotCapture);
+            this._bubbleNotifyPrimPointerObserver(primitive, PrimitivePointerInfo.PointerGotCapture, null);
 
             this._capturedPointers.add(pointerId.toString(), primitive);
             return true;
@@ -230,7 +231,7 @@
             }
 
             this._primPointerInfo.updateRelatedTarget(primitive, Vector2.Zero());
-            this._bubbleNotifyPrimPointerObserver(primitive, PrimitivePointerInfo.PointerLostCapture);
+            this._bubbleNotifyPrimPointerObserver(primitive, PrimitivePointerInfo.PointerLostCapture, null);
             this._capturedPointers.remove(pointerId.toString());
             return true;
         }
@@ -281,13 +282,13 @@
             // Analyze the pointer event type and fire proper events on the primitive
 
             if (eventData.type === PointerEventTypes.POINTERWHEEL) {
-                this._bubbleNotifyPrimPointerObserver(targetPrim, PrimitivePointerInfo.PointerMouseWheel);
+                this._bubbleNotifyPrimPointerObserver(targetPrim, PrimitivePointerInfo.PointerMouseWheel, <MouseWheelEvent>eventData.event);
             } else if (eventData.type === PointerEventTypes.POINTERMOVE) {
-                this._bubbleNotifyPrimPointerObserver(targetPrim, PrimitivePointerInfo.PointerMove);
+                this._bubbleNotifyPrimPointerObserver(targetPrim, PrimitivePointerInfo.PointerMove, <PointerEvent>eventData.event);
             } else if (eventData.type === PointerEventTypes.POINTERDOWN) {
-                this._bubbleNotifyPrimPointerObserver(targetPrim, PrimitivePointerInfo.PointerDown);
+                this._bubbleNotifyPrimPointerObserver(targetPrim, PrimitivePointerInfo.PointerDown, <PointerEvent>eventData.event);
             } else if (eventData.type === PointerEventTypes.POINTERUP) {
-                this._bubbleNotifyPrimPointerObserver(targetPrim, PrimitivePointerInfo.PointerUp);
+                this._bubbleNotifyPrimPointerObserver(targetPrim, PrimitivePointerInfo.PointerUp, <PointerEvent>eventData.event);
             }
         }
 
@@ -373,13 +374,13 @@
                 // Notify the previous "over" prim that the pointer is no longer over it
                 if ((capturedPrim && capturedPrim===prevPrim) || (!capturedPrim && prevPrim)) {
                     this._primPointerInfo.updateRelatedTarget(prevPrim, this._previousOverPrimitive.intersectionLocation);
-                    this._bubbleNotifyPrimPointerObserver(prevPrim, PrimitivePointerInfo.PointerOut);
+                    this._bubbleNotifyPrimPointerObserver(prevPrim, 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);
+                    this._bubbleNotifyPrimPointerObserver(actualPrim, PrimitivePointerInfo.PointerOver, null);
                 }
             }
 
@@ -415,8 +416,8 @@
             console.log(debug);
         }
 
-        private _bubbleNotifyPrimPointerObserver(prim: Prim2DBase, mask: number) {
-            let pii = this._primPointerInfo;
+        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
             if ((mask & (PrimitivePointerInfo.PointerOver | PrimitivePointerInfo.PointerOut)) !== 0) {
@@ -432,11 +433,12 @@
 
                     // Exec the observers
                     this._debugExecObserver(cur, mask);
-                    cur._pointerEventObservable.notifyObservers(pii, mask);
+                    cur._pointerEventObservable.notifyObservers(ppi, mask);
+                    this._triggerActionManager(cur, ppi, mask, eventData);
 
                     // 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 (pii.cancelBubble) {
+                    if (ppi.cancelBubble) {
                         if ((mask & (PrimitivePointerInfo.PointerOver | PrimitivePointerInfo.PointerOut)) === 0) {
                             return;
                         }
@@ -454,13 +456,13 @@
                 // Trigger a PointerEnter corresponding to the PointerOver
                 if (mask === PrimitivePointerInfo.PointerOver) {
                     this._debugExecObserver(cur, PrimitivePointerInfo.PointerEnter);
-                    cur._pointerEventObservable.notifyObservers(pii, PrimitivePointerInfo.PointerEnter);
+                    cur._pointerEventObservable.notifyObservers(ppi, PrimitivePointerInfo.PointerEnter);
                 }
 
                 // Trigger a PointerLeave corresponding to the PointerOut
                 else if (mask === PrimitivePointerInfo.PointerOut) {
                     this._debugExecObserver(cur, PrimitivePointerInfo.PointerLeave);
-                    cur._pointerEventObservable.notifyObservers(pii, PrimitivePointerInfo.PointerLeave);
+                    cur._pointerEventObservable.notifyObservers(ppi, PrimitivePointerInfo.PointerLeave);
                 }
 
                 // Loop to the parent
@@ -468,6 +470,95 @@
             }
         }
 
+        private _triggerActionManager(prim: Prim2DBase, ppi: PrimitivePointerInfo, mask: number, eventData) {
+
+            // Process Trigger related to PointerDown
+            if ((mask & PrimitivePointerInfo.PointerDown) !== 0) {
+                // On pointer down, record the current position and time to be able to trick PickTrigger and LongPressTrigger
+                this._pickStartingPosition = ppi.primitivePointerPos.clone();
+                this._pickStartingTime = new Date().getTime();
+                this._pickedDownPrim = null;
+
+                if (prim.actionManager) {
+                    this._pickedDownPrim = prim;
+                    if (prim.actionManager.hasPickTriggers) {
+                        let actionEvent = ActionEvent.CreateNewFromPrimitive(prim, ppi.primitivePointerPos, eventData);
+
+                        switch (eventData.button) {
+                        case 0:
+                            prim.actionManager.processTrigger(ActionManager.OnLeftPickTrigger, actionEvent);
+                            break;
+                        case 1:
+                            prim.actionManager.processTrigger(ActionManager.OnCenterPickTrigger, actionEvent);
+                            break;
+                        case 2:
+                            prim.actionManager.processTrigger(ActionManager.OnRightPickTrigger, actionEvent);
+                            break;
+                        }
+                        prim.actionManager.processTrigger(ActionManager.OnPickDownTrigger, actionEvent);
+                    }
+
+                    if (prim.actionManager.hasSpecificTrigger(ActionManager.OnLongPressTrigger)) {
+                        window.setTimeout(() => {
+                            let ppi = this._primPointerInfo;
+                            let capturedPrim = this.getCapturedPrimitive(ppi.pointerId);
+                            this._updateIntersectionList(ppi.canvasPointerPos, capturedPrim !== null);
+
+                            let ii = new IntersectInfo2D();
+                            ii.pickPosition = ppi.canvasPointerPos.clone();
+                            ii.findFirstOnly = false;
+                            this.intersect(ii);
+
+                            if (ii.isIntersected) {
+                                let iprim = ii.topMostIntersectedPrimitive.prim;
+                                if (iprim.actionManager) {
+                                    if (this._pickStartingTime !== 0 && ((new Date().getTime() - this._pickStartingTime) > ActionManager.LongPressDelay) && (Math.abs(this._pickStartingPosition.x - ii.pickPosition.x) < ActionManager.DragMovementThreshold && Math.abs(this._pickStartingPosition.y - ii.pickPosition.y) < ActionManager.DragMovementThreshold)) {
+                                        this._pickStartingTime = 0;
+                                        iprim.actionManager.processTrigger(ActionManager.OnLongPressTrigger, ActionEvent.CreateNewFromPrimitive(prim, ppi.primitivePointerPos, eventData));
+                                    }
+                                }
+                            }
+                        }, ActionManager.LongPressDelay);
+                    }
+                }
+            }
+
+            // Process Triggers related to Pointer Up
+            else if ((mask & PrimitivePointerInfo.PointerUp) !== 0) {
+                this._pickStartingTime = 0;
+
+                let actionEvent = ActionEvent.CreateNewFromPrimitive(prim, ppi.primitivePointerPos, eventData);
+                if (prim.actionManager) {
+                    // OnPickUpTrigger
+                    prim.actionManager.processTrigger(ActionManager.OnPickUpTrigger, actionEvent);
+
+                    // OnPickTrigger
+                    if (Math.abs(this._pickStartingPosition.x - ppi.canvasPointerPos.x) < ActionManager.DragMovementThreshold && Math.abs(this._pickStartingPosition.y - ppi.canvasPointerPos.y) < ActionManager.DragMovementThreshold) {
+                        prim.actionManager.processTrigger(ActionManager.OnPickTrigger, actionEvent);
+                    }
+                }
+
+                // OnPickOutTrigger
+                if (this._pickedDownPrim && this._pickedDownPrim.actionManager && (this._pickedDownPrim !== prim)) {
+                    this._pickedDownPrim.actionManager.processTrigger(ActionManager.OnPickOutTrigger, actionEvent);
+                }
+            }
+
+            else if ((mask & PrimitivePointerInfo.PointerOver) !== 0) {
+                if (prim.actionManager) {
+                    let actionEvent = ActionEvent.CreateNewFromPrimitive(prim, ppi.primitivePointerPos, eventData);
+                    prim.actionManager.processTrigger(ActionManager.OnPointerOverTrigger, actionEvent);
+                }
+            }
+
+            else if ((mask & PrimitivePointerInfo.PointerOut) !== 0) {
+                if (prim.actionManager) {
+                    let actionEvent = ActionEvent.CreateNewFromPrimitive(prim, ppi.primitivePointerPos, eventData);
+                    prim.actionManager.processTrigger(ActionManager.OnPointerOutTrigger, actionEvent);
+                }
+            }
+        }
+
         _notifChildren(prim: Prim2DBase, mask: number) {
             let pii = this._primPointerInfo;
 
@@ -673,6 +764,9 @@
         private _updateRenderId: number;
         private _intersectionRenderId: number;
         private _hoverStatusRenderId: number;
+        private _pickStartingPosition: Vector2;
+        private _pickedDownPrim: Prim2DBase;
+        private _pickStartingTime: number;
         private _previousIntersectionList: Array<PrimitiveIntersectedInfo>;
         private _actualIntersectionList: Array<PrimitiveIntersectedInfo>;
         private _previousOverPrimitive: PrimitiveIntersectedInfo;

+ 43 - 2
src/Canvas2d/babylon.prim2dBase.ts

@@ -327,6 +327,14 @@
             this.origin = new Vector2(0.5, 0.5);
         }
 
+
+        public get actionManager(): ActionManager {
+            if (!this._actionManager) {
+                this._actionManager = new ActionManager(this.owner.scene);
+            }
+            return this._actionManager;
+        }
+
         /**
          * From 'this' primitive, traverse up (from parent to parent) until the given predicate is true
          * @param predicate the predicate to test on each parent
@@ -371,12 +379,39 @@
             return this._id;
         }
 
+        /**
+         * Metadata of the position property
+         */
         public static positionProperty: Prim2DPropInfo;
+
+        /**
+         * Metadata of the rotation property
+         */
         public static rotationProperty: Prim2DPropInfo;
+
+        /**
+         * Metadata of the scale property
+         */
         public static scaleProperty: Prim2DPropInfo;
+
+        /**
+         * Metadata of the origin property
+         */
         public static originProperty: Prim2DPropInfo;
+
+        /**
+         * Metadata of the levelVisible property
+         */
         public static levelVisibleProperty: Prim2DPropInfo;
+
+        /**
+         * Metadata of the isVisible property
+         */
         public static isVisibleProperty: Prim2DPropInfo;
+
+        /**
+         * Metadata of the zOrder property
+         */
         public static zOrderProperty: Prim2DPropInfo;
 
         @instanceLevelProperty(1, pi => Prim2DBase.positionProperty = pi, false, true)
@@ -686,6 +721,11 @@
                 return false;
             }
 
+            if (!this._actionManager) {
+                this._actionManager.dispose();
+                this._actionManager = null;
+            }
+
             // If there's a parent, remove this object from its parent list
             if (this._parent) {
                 let i = this._parent._children.indexOf(this);
@@ -716,7 +756,7 @@
         }
 
         public _needPrepare(): boolean {
-            return (this.isVisible || this._visibilityChanged) && (this._modelDirty || (this._instanceDirtyFlags !== 0) || (this._globalTransformProcessStep !== this._globalTransformStep));
+            return this._visibilityChanged && (this._modelDirty || (this._instanceDirtyFlags !== 0) || (this._globalTransformProcessStep !== this._globalTransformStep));
         }
 
         public _prepareRender(context: Render2DContext) {
@@ -806,7 +846,7 @@
                 this.isVisible = (!this._parent || this._parent.isVisible) && this.levelVisible;
 
                 // Detect a change of visibility
-                this._visibilityChanged = (curVisibleState !== undefined) && curVisibleState !== this.isVisible;
+                this._visibilityChanged = curVisibleState !== this.isVisible;
 
                 // Get/compute the localTransform
                 let localDirty = this._updateLocalTransform();
@@ -831,6 +871,7 @@
 
         private _owner: Canvas2D;
         private _parent: Prim2DBase;
+        private _actionManager: ActionManager;
         protected _children: Array<Prim2DBase>;
         private _renderGroup: Group2D;
         private _hierarchyDepth: number;

+ 54 - 2
src/Canvas2d/babylon.smartPropertyPrim.ts

@@ -145,30 +145,54 @@
             this.animations = new Array<Animation>();
         }
 
+        /**
+         * An observable that is triggered when a property (using of the XXXXLevelProperty decorator) has its value changing.
+         * You can add an observer that will be triggered only for a given set of Properties using the Mask feature of the Observable and the corresponding Prim2DPropInfo.flagid value (e.g. Prim2DBase.positionProperty.flagid|Prim2DBase.rotationProperty.flagid to be notified only about position or rotation change)
+         */
         public propertyChanged: Observable<PropertyChangedInfo>;
 
+        /**
+         * Check if the object is disposed or not.
+         * @returns true if the object is dispose, false otherwise.
+         */
         public get isDisposed(): boolean {
             return this._isDisposed;
         }
 
+        /**
+         * Disposable pattern, this method must be overloaded by derived types in order to clean up hardware related resources.
+         * @returns false if the object is already dispose, true otherwise. Your implementation must call super.dispose() and check for a false return and return immediately if it's the case.
+         */
         public dispose(): boolean {
             if (this.isDisposed) {
                 return false;
             }
 
+            // Don't set to null, it may upset somebody...
+            this.animations.splice(0);
+
             this._isDisposed = true;
             return true;
         }
 
-        public animations;
+        /**
+         * Animation array, more info: http://doc.babylonjs.com/tutorials/Animations
+         */
+        public animations: Animation[];
 
         /**
-         * Returns as a new array populated with the Animatable used by the primitive. Must be overridden.
+         * Returns as a new array populated with the Animatable used by the primitive. Must be overloaded by derived primitives.
+         * Look at Sprite2D for more information
          */
         public getAnimatables(): IAnimatable[] {
             return new Array<IAnimatable>();
         }
 
+        /**
+         * Property giving the Model Key associated to the property.
+         * This value is constructed from the type of the primitive and all the name/value of its properties declared with the modelLevelProperty decorator
+         * @returns the model key string.
+         */
         public get modelKey(): string {
 
             // No need to compute it?
@@ -191,10 +215,18 @@
             return modelKey;
         }
 
+        /**
+         * States if the Primitive is dirty and should be rendered again next time.
+         * @returns true is dirty, false otherwise
+         */
         public get isDirty(): boolean {
             return (this._instanceDirtyFlags !== 0) || this._modelDirty;
         }
 
+        /**
+         * Access the dictionary of properties metadata. Only properties decorated with XXXXLevelProperty are concerned
+         * @returns the dictionary, the key is the property name as declared in Javascript, the value is the metadata object
+         */
         private get propDic(): StringDictionary<Prim2DPropInfo> {
             if (!this._propInfo) {
                 let cti = ClassTreeInfo.get<Prim2DClassInfo, Prim2DPropInfo>(Object.getPrototypeOf(this));
@@ -332,15 +364,29 @@
 
         }
 
+        /**
+         * Check if a given set of properties are dirty or not.
+         * @param flags a ORed combination of Prim2DPropInfo.flagId values
+         * @return true if at least one property is dirty, false if none of them are.
+         */
         public checkPropertiesDirty(flags: number): boolean {
             return (this._instanceDirtyFlags & flags) !== 0;
         }
 
+        /**
+         * Clear a given set of properties.
+         * @param flags a ORed combination of Prim2DPropInfo.flagId values
+         * @return the new set of property still marked as dirty
+         */
         protected clearPropertiesDirty(flags: number): number {
             this._instanceDirtyFlags &= ~flags;
             return this._instanceDirtyFlags;
         }
 
+        /**
+         * Retrieve the boundingInfo for this Primitive, computed based on the primitive itself and NOT its children
+         * @returns {} 
+         */
         public get levelBoundingInfo(): BoundingInfo2D {
             if (this._levelBoundingInfoDirty) {
                 this.updateLevelBoundingInfo();
@@ -349,10 +395,16 @@
             return this._levelBoundingInfo;
         }
 
+        /**
+         * This method must be overridden by a given Primitive implementation to compute its boundingInfo
+         */
         protected updateLevelBoundingInfo() {
 
         }
 
+        /**
+         * Property method called when the Primitive becomes dirty
+         */
         protected onPrimBecomesDirty() {
 
         }

+ 2 - 6
src/Canvas2d/babylon.text2d.ts

@@ -25,7 +25,7 @@
             engine.bindBuffers(this.vb, this.ib, [1], 4, this.effect);
 
             var cur = engine.getAlphaMode();
-            engine.setAlphaMode(Engine.ALPHA_COMBINE);
+            engine.setAlphaMode(Engine.ALPHA_ADD);
             let count = instanceInfo._instancesPartsData[0].usedElementCount;
             if (instanceInfo._owner.owner.supportInstancedArray) {
                 engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[0], null, this.instancingAttributes);
@@ -227,7 +227,6 @@
             this.hAlign = hAlign;
             this._tabulationSize = tabulationSize;
             this._isTransparent = true;
-            this.origin = Vector2.Zero();
         }
 
         public static Create(parent: Prim2DBase, id: string, x: number, y: number, fontName: string, text: string, defaultFontColor?: Color4, areaSize?: Size, vAlign = Text2D.TEXT2D_VALIGN_TOP, hAlign = Text2D.TEXT2D_HALIGN_LEFT, tabulationSize: number = 4): Text2D {
@@ -267,9 +266,7 @@
 
             // Effects
             let ei = this.getDataPartEffectInfo(Text2D.TEXT2D_MAINPARTID, ["index"]);
-            renderCache.effect = engine.createEffect("text2d", ei.attributes, ei.uniforms, ["diffuseSampler"], ei.defines, null, e => {
-//                renderCache.setupUniformsLocation(e, ei.uniforms, Text2D.TEXT2D_MAINPARTID);
-            });
+            renderCache.effect = engine.createEffect("text2d", ei.attributes, ei.uniforms, ["diffuseSampler"], ei.defines, null);
 
             return renderCache;
         }
@@ -310,7 +307,6 @@
                 let charxpos = 0;
                 d.dataElementCount = this._charCount;
                 d.curElement = 0;
-                let customOrigin = Vector2.Zero();
                 for (let char of this.text) {
 
                     // Line feed

+ 2 - 2
src/Materials/Textures/babylon.fontTexture.ts

@@ -56,7 +56,7 @@
                 return ft;
             }
 
-            ft = new FontTexture(null, lfn, scene);
+            ft = new FontTexture(null, lfn, scene, 200, Texture.NEAREST_SAMPLINGMODE);
             dic.add(lfn, ft);
 
             return ft;
@@ -174,7 +174,7 @@
 
             // Fill the CharInfo object
             info.topLeftUV = new Vector2(this._currentFreePosition.x / textureSize.width, this._currentFreePosition.y / textureSize.height);
-            info.bottomRightUV = new Vector2(info.topLeftUV.x + (width / textureSize.width), info.topLeftUV.y + ((this._lineHeight + 1) / textureSize.height));
+            info.bottomRightUV = new Vector2(info.topLeftUV.x + (width / textureSize.width), info.topLeftUV.y + ((this._lineHeight + 2) / textureSize.height));
             info.charWidth = width;
 
             // Add the info structure

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

@@ -52,7 +52,7 @@ void main(void) {
 
 	vColor = color;
 	vec4 pos;
-	pos.xy = (pos2.xy - origin) * sizeUV * textureSize;
+	pos.xy = (pos2.xy * sizeUV * textureSize) - origin;
 	pos.z = 1.0;
 	pos.w = 1.0;
 	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, zBias.y);