//////////////////////////////////////////////////////////////////////////////// // // Licensed to the Apache Software Foundation (ASF) under one or more // contributor license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright ownership. // The ASF licenses this file to You under the Apache License, Version 2.0 // (the "License"); you may not use this file except in compliance with // the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// package mx.effects.effectClasses { import flash.display.BitmapData; import flash.display.DisplayObject; import flash.display.Graphics; import flash.display.Shape; import flash.events.Event; import flash.filters.DropShadowFilter; import flash.geom.Matrix; import flash.geom.Rectangle; import flash.utils.getTimer; import mx.controls.SWFLoader; import mx.core.FlexShape; import mx.core.IContainer; import mx.core.IInvalidating; import mx.core.IUIComponent; import mx.core.mx_internal; import mx.effects.EffectInstance; import mx.effects.EffectManager; import mx.effects.Tween; import mx.events.FlexEvent; import mx.events.ResizeEvent; import mx.events.TweenEvent; use namespace mx_internal; /** * The MaskEffectInstance class is an abstract base class * that implements the instance class for * the MaskEffect class. *

Every effect class that is a subclass of the TweenEffect class * supports the following events:

* * * *

The event object passed to the event listener for these events is of type TweenEvent. * The TweenEvent class defines the property value, which contains * the tween value calculated by the effect. * For the Mask effect, * the TweenEvent.value property contains a 4-item Array, where:

* * * @see mx.effects.MaskEffect * @see mx.events.TweenEvent * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public class MaskEffectInstance extends EffectInstance { include "../../core/Version.as"; //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Constructor. * * @param target The Object to animate with this effect. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function MaskEffectInstance(target:Object) { super(target); } //-------------------------------------------------------------------------- // // Variables // //-------------------------------------------------------------------------- /** * Contains the effect mask, either the default mask created * by the defaultCreateMask() method, * or the one specified by the function passed to the * createMaskFunction property. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected var effectMask:Shape; /** * The actual size of the effect target, including any drop shadows. * Flex calculates the value of this property; you do not have to set it. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected var targetVisualBounds:Rectangle; /** * @private */ private var effectMaskRefCount:Number = 0; /** * @private */ private var invalidateBorder:Boolean = false; /** * @private */ private var moveTween:Tween; /** * @private */ private var origMask:DisplayObject; /** * @private */ private var origScrollRect:Rectangle; /** * @private */ private var scaleTween:Tween; /** * @private */ private var tweenCount:int = 0; /** * @private */ private var currentMoveTweenValue:Object; /** * @private */ private var currentScaleTweenValue:Object; /** * @private */ private var MASK_NAME:String = "_maskEffectMask"; /** * @private */ private var dispatchedStartEvent:Boolean = false; /** * @private */ private var useSnapshotBounds:Boolean = true; /** * @private */ private var stoppedEarly:Boolean = false; /** * @private */ mx_internal var persistAfterEnd:Boolean = false; //-------------------------------------------------------------------------- // // Properties // //-------------------------------------------------------------------------- //-------------------------------------------------------------------------- // createMaskFunction //-------------------------------------------------------------------------- /** * @private * Storage for the createMaskFunction property. */ private var _createMaskFunction:Function; /** * Function called when the effect creates the mask. * The default value is a function that returns a Rectangle * with the same dimensions as the effect target. * *

You can use this property to specify your own callback function to draw the mask. * The function must have the following signature:

* *
	 *  public function createLargeMask(targ:Object, bounds:Rectangle):Shape {
	 *    var myMask:Shape = new Shape();
	 *    // Create mask.
	 *  
	 *    return myMask;
	 *  }
	 *  
* *

You set this property to the name of the function, * as the following example shows for the WipeLeft effect:

* *
	 *    <mx:WipeLeft id="showWL" createMaskFunction="createLargeMask" showTarget="false"/>
* * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get createMaskFunction():Function { return _createMaskFunction != null ? _createMaskFunction : defaultCreateMask; } /** * @private */ public function set createMaskFunction(value:Function):void { _createMaskFunction = value; } //---------------------------------- // moveEasingFunction //---------------------------------- /** * Easing function to use for moving the mask. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public var moveEasingFunction:Function; //-------------------------------------------------------------------------- // playheadTime //-------------------------------------------------------------------------- /** * @private */ override public function get playheadTime():Number { var value:Number; if (moveTween) value = moveTween.playheadTime; else if (scaleTween) value = scaleTween.playheadTime; else return 0; return value + super.playheadTime; } //-------------------------------------------------------------------------- // playReversed //-------------------------------------------------------------------------- /** * @private */ override mx_internal function set playReversed(value:Boolean):void { if (moveTween) moveTween.playReversed = value; if (scaleTween) scaleTween.playReversed = value; super.playReversed = value; } //---------------------------------- // scaleEasingFunction //---------------------------------- /** * Easing function to use for scaling the mask. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public var scaleEasingFunction:Function; //---------------------------------- // scaleXFrom //---------------------------------- /** * Initial scaleX for mask. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public var scaleXFrom:Number; //---------------------------------- // scaleXTo //---------------------------------- /** * Ending scaleX for mask. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public var scaleXTo:Number; //---------------------------------- // scaleYFrom //---------------------------------- /** * Initial scaleY for mask. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public var scaleYFrom:Number; //---------------------------------- // scaleYTo //---------------------------------- /** * Ending scaleY for mask. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public var scaleYTo:Number; //---------------------------------- // showTarget //---------------------------------- [Inspectable(category="General", defaultValue="true")] /** * @private * Storage for the showTarget property. */ private var _showTarget:Boolean = true; /** * @private */ private var _showExplicitlySet:Boolean = false; /** * Specifies that the target component is becoming visible, * false, or invisible, true. * * @default true * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get showTarget():Boolean { return _showTarget; } /** * @private */ public function set showTarget(value:Boolean):void { _showTarget = value; _showExplicitlySet = true; } //---------------------------------- // targetArea //---------------------------------- /** * The area where the mask is applied on the target. * The dimensions are relative to the target itself. * By default, the area is the entire target and is created like this: * new Rectangle(0, 0, target.width, target.height); * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public var targetArea:Rectangle; //---------------------------------- // xFrom //---------------------------------- /** * Initial position's x coordinate for mask. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public var xFrom:Number; //---------------------------------- // xTo //---------------------------------- /** * Destination position's x coordinate for mask. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public var xTo:Number; //---------------------------------- // yFrom //---------------------------------- /** * Initial position's y coordinate for mask. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public var yFrom:Number; //---------------------------------- // yTo //---------------------------------- /** * Destination position's y coordinate for mask. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public var yTo:Number; //-------------------------------------------------------------------------- // // Overridden methods // //-------------------------------------------------------------------------- /** * @private */ override public function initEffect(event:Event):void { super.initEffect(event); switch (event.type) { case "childrenCreationComplete": case FlexEvent.CREATION_COMPLETE: case FlexEvent.SHOW: case Event.ADDED: case "resizeEnd": { showTarget = true; break; } case FlexEvent.HIDE: case Event.REMOVED: case "resizeStart": { showTarget = false; break; } case Event.RESIZE: { // don't use the snapshot because it will be the wrong size useSnapshotBounds = false; break; } } } /** * @private */ override public function startEffect():void { // Init the mask only once when the effect is played. initMask(); // Register to be notified if the target object is resized. target.addEventListener(ResizeEvent.RESIZE, eventHandler); // This will call playEffect eventually. super.startEffect(); } /** * @private */ override public function play():void { super.play(); // This allows the MaskEffect subclass to set the effect properties. initMaskEffect(); EffectManager.startVectorEffect(IUIComponent(target)); //EffectManager.startBitmapEffect(target); // Move Tween if (!isNaN(xFrom) && !isNaN(yFrom) && !isNaN(xTo) && !isNaN(yTo)) { tweenCount++; moveTween = new Tween(this, [ xFrom, yFrom ], [ xTo, yTo ], duration, -1, onMoveTweenUpdate, onMoveTweenEnd); moveTween.playReversed = playReversed; // If the caller supplied their own easing equation, override the // one that's baked into Tween. if (moveEasingFunction != null) moveTween.easingFunction = moveEasingFunction; } // Scale Tween if (!isNaN(scaleXFrom) && !isNaN(scaleYFrom) && !isNaN(scaleXTo) && !isNaN(scaleYTo)) { tweenCount++; scaleTween = new Tween(this, [ scaleXFrom, scaleYFrom ], [ scaleXTo, scaleYTo ], duration, -1, onScaleTweenUpdate, onScaleTweenEnd); scaleTween.playReversed = playReversed; // If the caller supplied their own easing equation, override the // one that's baked into Tween. if (scaleEasingFunction != null) scaleTween.easingFunction = scaleEasingFunction; } dispatchedStartEvent = false; // Call these after tween creation so that saveTweenValues knows which values to dispatch if (moveTween) { // Set the animation to the initial value // before the screen refreshes. onMoveTweenUpdate(moveTween.getCurrentValue(0)); } if (scaleTween) { // Set the animation to the initial value // before the screen refreshes. onScaleTweenUpdate(scaleTween.getCurrentValue(0)); } } /** * Pauses the effect until you call the resume() method. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ override public function pause():void { super.pause(); if (moveTween) moveTween.pause(); if (scaleTween) scaleTween.pause(); } /** * @private */ override public function stop():void { EffectManager.endVectorEffect(IUIComponent(target)); stoppedEarly = true; super.stop(); if (moveTween) moveTween.stop(); if (scaleTween) scaleTween.stop(); } /** * Resumes the effect after it has been paused * by a call to the pause() method. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ override public function resume():void { super.resume(); if (moveTween) moveTween.resume(); if (scaleTween) scaleTween.resume(); } /** * Plays the effect in reverse, * starting from the current position of the effect. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ override public function reverse():void { super.reverse(); if (moveTween) moveTween.reverse(); if (scaleTween) scaleTween.reverse(); super.playReversed = !playReversed; } /** * @private */ override public function end():void { stopRepeat = true; if (moveTween) moveTween.endTween(); if (scaleTween) scaleTween.endTween(); } /** * @private */ override public function finishEffect():void { target.removeEventListener(ResizeEvent.RESIZE, eventHandler); if (!persistAfterEnd && !stoppedEarly) removeMask(); super.finishEffect(); } //-------------------------------------------------------------------------- // // Methods // //-------------------------------------------------------------------------- /** * @private */ private function initMask():void { if (!effectMask) { if (useSnapshotBounds) targetVisualBounds = getVisibleBounds(DisplayObject(target)); else targetVisualBounds = new Rectangle(0, 0, target.width, target.height); effectMask = createMaskFunction(target, targetVisualBounds); // For Containers we need to add the mask // to the "allChildren" collection so it doesn't get // treated as a content child. if (target is IContainer) target.rawChildren.addChild(effectMask); else target.addChild(effectMask); effectMask.name = MASK_NAME; effectMaskRefCount = 0; } effectMask.x = 0; effectMask.y = 0; effectMask.alpha = .3; effectMask.visible = false; // If this object already had a transparency mask, then save off // the original mask, so that we can restore it when we're done. if (effectMaskRefCount++ == 0) { if (target.mask) origMask = target.mask; target.mask = effectMask; if (target.scrollRect) { origScrollRect = target.scrollRect; target.scrollRect = null; } } invalidateBorder = target is IContainer && "border" in target && target["border"] != null && target["border"] is IInvalidating && DisplayObject(target["border"]).filters != null; } /** * Creates the default mask for the effect. * * @param targ The effect target. * @param bounds The actual visual bounds of the target which includes drop shadows * * @return A Shape object that defines the mask. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function defaultCreateMask(targ:Object, bounds:Rectangle):Shape { // By default, create a mask that is the shape of the target. var targetWidth:Number = bounds.width / Math.abs(targ.scaleX); var targetHeight:Number = bounds.height / Math.abs(targ.scaleY); if (targ is SWFLoader) { // Make sure the loader's content has been sized targ.validateDisplayList(); if (targ.content) { targetWidth = targ.contentWidth; targetHeight = targ.contentHeight; } } var newMask:Shape = new FlexShape(); var g:Graphics = newMask.graphics; g.beginFill(0xFFFF00); g.drawRect(0, 0, targetWidth, targetHeight); g.endFill(); if (target.rotation == 0) { newMask.width = targetWidth; newMask.height = targetHeight; } else { var angle:Number = targ.rotation * Math.PI / 180; var sin:Number = Math.sin(angle); var cos:Number = Math.cos(angle); newMask.width = Math.abs(targetWidth * cos - targetHeight * sin); newMask.height = Math.abs(targetWidth * sin + targetHeight * cos); } return newMask; } /** * Initializes the move and scale * properties of the effect. * All subclasses should override this function. * Flex calls it after the mask has been created, * but before the tweens are created. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function initMaskEffect():void { if (!_showExplicitlySet && propertyChanges && propertyChanges.start["visible"] !== undefined) { _showTarget = !propertyChanges.start["visible"]; } } /** * @private * Returns a rectangle that describes the visible region of the component, including any dropshadows */ private function getVisibleBounds(targ:DisplayObject):Rectangle { var bitmap:BitmapData = new BitmapData(targ.width + 200, targ.height + 200, true, 0x00000000); var m:Matrix = new Matrix(); m.translate(100, 100); bitmap.draw(targ, m); var actualBounds:Rectangle = bitmap.getColorBoundsRect(0xFF000000, 0x00000000, false); actualBounds.x = actualBounds.x - 100; actualBounds.y = actualBounds.y - 100; bitmap.dispose(); if (actualBounds.width < targ.width) { actualBounds.width = targ.width; actualBounds.x = 0; } if (actualBounds.height < targ.height) { actualBounds.height = targ.height; actualBounds.y = 0; } return actualBounds; } /** * Callback method that is called when the x and y position * of the mask should be updated by the effect. * You do not call this method directly. * This method implements the method of the superclass. * * @param value Contains an interpolated * x and y value for the mask position, where value[0] * contains the new x position of the mask, * and value[1] contains the new y position. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function onMoveTweenUpdate(value:Object):void { saveTweenValue(value,null); if (effectMask) { effectMask.x = value[0]; effectMask.y = value[1]; } if (invalidateBorder) IInvalidating(target["border"]).invalidateDisplayList(); } /** * Callback method that is called when the x and y position * of the mask should be updated by the effect for the last time. * You do not call this method directly. * This method implements the method of the superclass. * * @param value Contains the final * x and y value for the mask position, where value[0] * contains the x position of the mask, * and value[1] contains the y position. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function onMoveTweenEnd(value:Object):void { onMoveTweenUpdate(value); finishTween(); } /** * Callback method that is called when the * scaleX and scaleY properties * of the mask should be updated by the effect. * You do not call this method directly. * This method implements the method of the superclass. * * @param value Contains an interpolated * scaleX and scaleY value for the mask, * where value[0] * contains the new scaleX value of the mask, * and value[1] contains the new scaleY value. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function onScaleTweenUpdate(value:Object):void { saveTweenValue(null, value); if (effectMask) { effectMask.scaleX = value[0]; effectMask.scaleY = value[1]; } } /** * Callback method that is called when the * scaleX and scaleY properties * of the mask should be updated by the effect for the last time. * You do not call this method directly. * This method implements the method of the superclass. * * @param value Contains the final * scaleX and scaleY value for the mask, * where value[0] * contains the scaleX value of the mask, * and value[1] contains the scaleY value. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function onScaleTweenEnd(value:Object):void { onScaleTweenUpdate(value); finishTween(); } /** * @private */ private function finishTween():void { if (tweenCount == 0 || --tweenCount == 0) { EffectManager.endVectorEffect(IUIComponent(target)); var values:Array = []; var value:Object; if (moveTween) { value = moveTween.getCurrentValue(duration); values.push(value[0]); values.push(value[1]); } else { values.push(null); values.push(null); } if (scaleTween) { value = scaleTween.getCurrentValue(duration); values.push(value[0]); values.push(value[1]); } else { values.push(null); values.push(null); } dispatchEvent(new TweenEvent(TweenEvent.TWEEN_END, false, false, values)); finishRepeat(); } } /** * @private */ private function removeMask():void { // Although it wasn't the original intended design, it turns out that // two mask effects can play simultaneously inside a effect. // The only gotcha is that we shouldn't clear the mask until both // effects are done. The solution: a reference count. if (--effectMaskRefCount == 0) { if (origMask == null || (origMask && origMask.name != MASK_NAME)) target.mask = origMask; if (origScrollRect) { target.scrollRect = origScrollRect; } if (target is IContainer) target.rawChildren.removeChild(effectMask); else target.removeChild(effectMask); effectMask = null; } } /** * @private */ private function saveTweenValue(moveValue:Object, scaleValue:Object):void { if (moveValue != null) { currentMoveTweenValue = moveValue; } else if (scaleValue != null) { currentScaleTweenValue = scaleValue; } if ((moveTween == null || currentMoveTweenValue != null) && (scaleTween == null || currentScaleTweenValue != null)) { var values:Array = []; if (currentMoveTweenValue) { values.push(currentMoveTweenValue[0]); values.push(currentMoveTweenValue[1]); } else { values.push(null); values.push(null); } if (currentScaleTweenValue) { values.push(currentScaleTweenValue[0]); values.push(currentScaleTweenValue[1]); } else { values.push(null); values.push(null); } if (!dispatchedStartEvent) { dispatchEvent(new TweenEvent(TweenEvent.TWEEN_START)); dispatchedStartEvent = true; } dispatchEvent(new TweenEvent(TweenEvent.TWEEN_UPDATE, false, false, values)); currentMoveTweenValue = null; currentScaleTweenValue = null; } } //-------------------------------------------------------------------------- // // Overridden event handlers // //-------------------------------------------------------------------------- /** * @private */ override mx_internal function eventHandler(event:Event):void { super.eventHandler(event); // This function is called if the target object is resized. if (event.type == ResizeEvent.RESIZE) { var tween:Tween = moveTween; if (!tween && scaleTween) tween = scaleTween; if (tween) { // Remember the amount of the effect that has already been // played. var elapsed:Number = getTimer() - tween.startTime; // Destroy the old tween object. Set its listener to a dummy // object, so that the onTweenEnd function is not called. if (moveTween) Tween.removeTween(moveTween); if (scaleTween) Tween.removeTween(scaleTween); // Reset the tween count tweenCount = 0; removeMask(); // The onTweenEnd function wasn't called, so decrement the // effectMaskRefCount here to keep it in balance. //effectMaskRefCount--; // Restart the effect and create a new mask. This is necessary // so that the mask's size matches the target object's new size. initMask(); play(); // Set the tween's clock, so that it thinks 'elapsed' // milliseconds of the animation have already played. if (moveTween) { moveTween.startTime -= elapsed; // Update the screen before a repaint occurs moveTween.doInterval(); } if (scaleTween) { scaleTween.startTime -= elapsed; // Update the screen before a repaint occurs scaleTween.doInterval(); } } } } } }