//////////////////////////////////////////////////////////////////////////////// // // 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:
* *tweenEnd
: Dispatched when the tween effect ends. tweenUpdate
: Dispatched every time a TweenEffect
* class calculates a new value.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:
x
property.y
property.scaleX
property.scaleY
property.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