blob: 76aad67c9caab685f1b6fbc397c7988423b1933d [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// 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.events.Event;
import mx.containers.Panel;
import mx.core.Container;
import mx.core.FlexGlobals;
import mx.core.IUIComponent;
import mx.core.ScrollPolicy;
import mx.core.mx_internal;
import mx.effects.EffectManager;
import mx.events.EffectEvent;
import mx.events.ResizeEvent;
import mx.styles.IStyleClient;
use namespace mx_internal;
/**
* The ResizeInstance class implements the instance class
* for the Resize effect.
* Flex creates an instance of this class when it plays a Resize effect;
* you do not create one yourself.
*
* <p>Every effect class that is a subclass of the TweenEffect class
* supports the following events:</p>
*
* <ul>
* <li><code>tweenEnd</code>: Dispatched when the tween effect ends. </li>
*
* <li><code>tweenUpdate</code>: Dispatched every time a TweenEffect
* class calculates a new value.</li>
* </ul>
*
* <p>The event object passed to the event listener for these events is of type TweenEvent.
* The TweenEvent class defines the property <code>value</code>, which contains
* the tween value calculated by the effect.
* For the Resize effect,
* the <code>TweenEvent.value</code> property contains a 2-item Array, where: </p>
* <ul>
* <li>value[0]:Number A value between the values of the <code>Resize.widthFrom</code>
* and <code>Resize.widthTo</code> property.</li>
*
* <li>value[1]:Number A value between the values of the <code>Resize.heightFrom</code>
* and <code>Resize.heightTo</code> property.</li>
* </ul>
*
* @see mx.effects.Resize
* @see mx.events.TweenEvent
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public class ResizeInstance extends TweenEffectInstance
{
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 ResizeInstance(target:Object)
{
super(target);
needToLayout = true;
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* @private
*/
private var restoreVisibleArray:Array;
/**
* @private
*/
private var restoreAutoLayoutArray:Array;
/**
* @private
*/
private var numHideEffectsPlaying:Number = 0;
/**
* @private
*/
private var origPercentHeight:Number;
/**
* @private
*/
private var origPercentWidth:Number;
/**
* @private
*/
private var origExplicitHeight:Number;
/**
* @private
*/
private var origExplicitWidth:Number;
/**
* @private
*/
private var heightSet:Boolean;
/**
* @private
*/
private var widthSet:Boolean;
/**
* @private
*/
private var explicitWidthSet:Boolean;
/**
* @private
*/
private var explicitHeightSet:Boolean;
/**
* @private
*/
private var origVerticalScrollPolicy:String = "";
/**
* @private
*/
private var origHorizontalScrollPolicy:String = "";
/**
* @private
*/
private var parentOrigVerticalScrollPolicy:String = "";
/**
* @private
*/
private var parentOrigHorizontalScrollPolicy:String = "";
/**
* @private
* Stores the left style of the target
*/
private var left:*;
/**
* @private
* Stores the right style of the target
*/
private var right:*;
/**
* @private
* Stores the top style of the target
*/
private var top:*;
/**
* @private
* Stores the bottom style of the target
*/
private var bottom:*;
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// heightBy
//----------------------------------
/**
* @private
* Storage for the heightBy property.
*/
private var _heightBy:Number;
/**
* Number of pixels by which to modify the height of the component.
* Values may be negative.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get heightBy():Number
{
return _heightBy;
}
/**
* @private
*/
public function set heightBy(value:Number):void
{
_heightBy = value;
heightSet = !isNaN(value);
}
//----------------------------------
// heightFrom
//----------------------------------
/**
* Initial height. If omitted, Flex uses the current size.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var heightFrom:Number;
//----------------------------------
// heightTo
//----------------------------------
/**
* @private
* Storage for the heightTo property.
*/
private var _heightTo:Number;
/**
* Final height, in pixels.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get heightTo():Number
{
return _heightTo;
}
/**
* @private
*/
public function set heightTo(value:Number):void
{
_heightTo = value;
heightSet = !isNaN(value);
}
//----------------------------------
// hideChildrenTargets
//----------------------------------
/**
* An Array of Panels.
* The children of these Panels are hidden while the Resize effect plays.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var hideChildrenTargets:Array /* of Panel */;
//----------------------------------
// widthBy
//----------------------------------
/**
* @private
* Storage for the widthBy property.
*/
private var _widthBy:Number;
/**
* Number of pixels by which to modify the width of the component.
* Values may be negative.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get widthBy():Number
{
return _widthBy;
}
/**
* @private
*/
public function set widthBy(value:Number):void
{
_widthBy = value;
widthSet = !isNaN(value);
}
//----------------------------------
// widthFrom
//----------------------------------
/**
* Initial width. If omitted, Flex uses the current size.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var widthFrom:Number;
//----------------------------------
// widthTo
//----------------------------------
/**
* @private
* Storage for the widthTo property.
*/
private var _widthTo:Number;
/**
* Final width, in pixels.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get widthTo():Number
{
return _widthTo;
}
/**
* @private
*/
public function set widthTo(value:Number):void
{
_widthTo = value;
widthSet = !isNaN(value);
}
//--------------------------------------------------------------------------
//
// Overridden methods
//
//--------------------------------------------------------------------------
/**
* @private
*/
override public function initEffect(event:Event):void
{
super.initEffect(event);
if (event is ResizeEvent && event.type == ResizeEvent.RESIZE)
{
if (isNaN(widthBy))
{
if (isNaN(widthFrom))
{
widthFrom = ResizeEvent(event).oldWidth;
}
if (isNaN(widthTo))
{
_widthTo = target.width;
}
}
if (isNaN(heightBy))
{
if (isNaN(heightFrom))
{
heightFrom = ResizeEvent(event).oldHeight;
}
if (isNaN(heightTo))
{
_heightTo = target.height;
}
}
}
}
/**
* @private
*/
override public function play():void
{
// Dispatch an effectStart event from the target.
super.play();
calculateExplicitDimensionChanges();
// If the target is a Panel, then find all Panel objects that will
// be affected by the animation. Deliver a "resizeStart" event to
// each affected Panel, and then wait until the Panel finishes
// hiding its children.
var childrenHiding:Boolean = hidePanelChildren();
if (target is IStyleClient)
{
left = target.getStyle("left");
if (left != undefined)
target.setStyle("left",undefined);
right = target.getStyle("right");
if (right != undefined)
target.setStyle("right",undefined);
top = target.getStyle("top");
if (top != undefined)
target.setStyle("top",undefined);
bottom = target.getStyle("bottom");
if (bottom != undefined)
target.setStyle("bottom",undefined);
}
if (!childrenHiding)
startResizeTween();
}
/**
* @private
*/
override public function onTweenUpdate(value:Object):void
{
EffectManager.suspendEventHandling();
// Use Math.round instead of Math.floor, so that the animation appears
// to begin playing after the value has changed by only half a pixel,
// instead of waiting for it to change by a whole pixel.
// Because of the way that the easing function mimics acceleration,
// it can take a while for the animation to get started.
target.width = Math.round(value[0]);
target.height = Math.round(value[1]);
// Set a flag indicating that LayoutManager.validateNow()
// should be called after we're finished processing
// all the effects for this frame.
if (tween)
tween.needToLayout = true;
needToLayout = true;
EffectManager.resumeEventHandling();
}
/**
* @private
*/
override public function onTweenEnd(value:Object):void
{
EffectManager.endVectorEffect(IUIComponent(target));
// Wait a frame before starting to restore the childrens' visibility.
// That way, we have a chance to run a measurement/layout pass with
// the final sizes and update the screen.
FlexGlobals.topLevelApplication.callLater(restorePanelChildren);
super.onTweenEnd(value);
EffectManager.suspendEventHandling();
var targetAsContainer:Container;
var parentAsContainer:Container;
// Restore the target's percent and explicit sizes
if (!heightSet)
{
target.percentHeight = origPercentHeight;
target.explicitHeight = origExplicitHeight;
if (origVerticalScrollPolicy != "")
{
targetAsContainer = target as Container;
if (targetAsContainer)
{
targetAsContainer.verticalScrollPolicy = origVerticalScrollPolicy;
origVerticalScrollPolicy = "";
}
}
if (parentOrigVerticalScrollPolicy != "" && target.parent)
{
parentAsContainer = target.parent as Container;
if (parentAsContainer)
{
parentAsContainer.verticalScrollPolicy = parentOrigVerticalScrollPolicy;
parentOrigVerticalScrollPolicy = "";
}
}
}
if (!widthSet)
{
target.percentWidth = origPercentWidth;
target.explicitWidth = origExplicitWidth;
if (origHorizontalScrollPolicy != "")
{
targetAsContainer = target as Container;
if (targetAsContainer)
{
targetAsContainer.horizontalScrollPolicy = origHorizontalScrollPolicy;
origHorizontalScrollPolicy = "";
}
}
if (parentOrigHorizontalScrollPolicy != "" && target.parent)
{
parentAsContainer = target.parent as Container;
if (parentAsContainer)
{
parentAsContainer.horizontalScrollPolicy = parentOrigHorizontalScrollPolicy;
parentOrigHorizontalScrollPolicy = "";
}
}
}
if (left != undefined)
target.setStyle("left",left);
if (right != undefined)
target.setStyle("right",right);
if (top != undefined)
target.setStyle("top",top);
if (bottom != undefined)
target.setStyle("bottom",bottom);
EffectManager.resumeEventHandling();
}
/**
* @private
*/
override public function end():void
{
// If we were waiting for the initial "hide children" effect to
// finish playing, then the tween might not be created yet.
// In that case, we need to explicitly jump to the end of the Resize,
// because the TweenEffect.end() function won't do it for us.
if (!tween)
{
calculateExplicitDimensionChanges();
onTweenEnd(playReversed ?
[ widthFrom, heightFrom ] :
[ widthTo, heightTo ]);
}
super.end();
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* @private
*/
private function startResizeTween():void
{
EffectManager.startVectorEffect(IUIComponent(target));
// Create a tween to resize the object
tween = createTween(this, [ widthFrom, heightFrom ],
[ widthTo, heightTo ], duration);
// Set back to initial size before the screen refreshes.
//EffectManager.suspendEventHandling();
applyTweenStartValues();
/*if (needToLayout)
UIComponent.layoutManager.validateNow();
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
//EffectManager.resumeEventHandling();
}
/**
* @private
* Hides children of Panels while the effect is playing.
*/
private function hidePanelChildren():Boolean
{
if (!hideChildrenTargets)
return false;
// Initialize a couple arrays that will be needed later
restoreVisibleArray = [];
restoreAutoLayoutArray = [];
// Send each panel a "resizeStart" event, which will trigger
// the resizeStartEffect (if any)
var n:int = hideChildrenTargets.length;
for (var i:int = 0; i < n; i++)
{
var p:Object = hideChildrenTargets[i];
if (p is Panel)
{
var prevNumHideEffectsPlaying:Number = numHideEffectsPlaying;
p.addEventListener(EffectEvent.EFFECT_START, eventHandler);
p.dispatchEvent(new Event("resizeStart"));
p.removeEventListener(EffectEvent.EFFECT_START, eventHandler);
// If no effect started playing, then make children invisible
// immediately instead of waiting for the end of the effect
if (numHideEffectsPlaying == prevNumHideEffectsPlaying)
makePanelChildrenInvisible(Panel(p), i);
}
}
return numHideEffectsPlaying > 0;
}
/**
* @private
*/
private function makePanelChildrenInvisible(panel:Container,
panelIndex:Number):void
{
var childArray:Array = [];
var child:IUIComponent;
// Hide the Panel's children while the Resize is occurring.
var n:int = panel.numChildren;
for (var i:int = 0; i < n; i++)
{
child = IUIComponent(panel.getChildAt(i));
if (child.visible)
{
childArray.push(child);
child.setVisible(false, true);
}
}
// Hide the Panel's scrollbars while the Resize is occurring.
child = panel.horizontalScrollBar;
if (child && child.visible)
{
childArray.push(child);
child.setVisible(false, true);
}
child = panel.verticalScrollBar;
if (child && child.visible)
{
childArray.push(child);
child.setVisible(false, true);
}
restoreVisibleArray[panelIndex] = childArray;
// Set autoLayout = false, which prevents the Panel's updateDisplayList()
// method from executing while the Panel is resizing.
if (panel.autoLayout)
{
panel.autoLayout = false;
restoreAutoLayoutArray[panelIndex] = true;
}
}
/**
* Method is used to explicitely determine widthTo and heightTo, taking into
* account the current state of the component and the inputs to this ResizeEffect
*
* @private
*/
private function calculateExplicitDimensionChanges():void
{
var explicitWidth:* = propertyChanges ? propertyChanges.end["explicitWidth"] : undefined;
var explicitHeight:* = propertyChanges ? propertyChanges.end["explicitHeight"] : undefined;
var percentWidth:* = propertyChanges ? propertyChanges.end["percentWidth"] : undefined;
var percentHeight:* = propertyChanges ? propertyChanges.end["percentHeight"] : undefined;
var targetAsContainer:Container;
var parentAsContainer:Container;
if (!heightSet)
{
// Determine the percentHeight/explicitHeight to apply to target when effect ends
if (percentHeight !== undefined)
origPercentHeight = percentHeight;
else
origPercentHeight = target.percentHeight;
if (isNaN(origPercentHeight))
{
if (explicitHeight !== undefined)
origExplicitHeight = explicitHeight;
else
origExplicitHeight = target.explicitHeight;
}
targetAsContainer = target as Container;
if (targetAsContainer && targetAsContainer.verticalScrollBar == null)
{
origVerticalScrollPolicy = targetAsContainer.verticalScrollPolicy;
targetAsContainer.verticalScrollPolicy = ScrollPolicy.OFF;
}
if (target.parent)
{
parentAsContainer = target.parent as Container;
if (parentAsContainer && parentAsContainer.verticalScrollBar == null)
{
parentOrigVerticalScrollPolicy = parentAsContainer.verticalScrollPolicy;
parentAsContainer.verticalScrollPolicy = ScrollPolicy.OFF;
}
}
}
if (!widthSet)
{
// Determine the percentHeight/explicitHeight to apply to target when effect ends
if (percentWidth !== undefined)
origPercentWidth = percentWidth;
else
origPercentWidth = target.percentWidth;
if (isNaN(origPercentWidth))
{
if (explicitWidth !== undefined)
origExplicitWidth = explicitWidth;
else
origExplicitWidth = target.explicitWidth;
}
targetAsContainer = target as Container;
if (targetAsContainer && targetAsContainer.horizontalScrollBar == null)
{
origHorizontalScrollPolicy = targetAsContainer.horizontalScrollPolicy;
targetAsContainer.horizontalScrollPolicy = ScrollPolicy.OFF;
}
if (target.parent)
{
parentAsContainer = target.parent as Container;
if (parentAsContainer && parentAsContainer.horizontalScrollBar == null)
{
parentOrigHorizontalScrollPolicy = parentAsContainer.horizontalScrollPolicy;
parentAsContainer.horizontalScrollPolicy = ScrollPolicy.OFF;
}
}
}
// The user may have supplied some combination of widthFrom,
// widthTo, and widthBy. If either widthFrom or widthTo is
// not explicitly defined, calculate its value based on the
// other two values.
if (isNaN(widthFrom))
{
if (!isNaN(widthTo) && !isNaN(widthBy))
widthFrom = widthTo - widthBy;
else if (propertyChanges && propertyChanges.start["width"] != undefined)
widthFrom = propertyChanges.start["width"];
else
widthFrom = target.width;
}
if (isNaN(widthTo))
{
if (isNaN(widthBy) &&
propertyChanges &&
(propertyChanges.end["width"] !== undefined ||
explicitWidth !== undefined ))
{
if (explicitWidth !== undefined && !isNaN(explicitWidth))
{
explicitWidthSet = true;
_widthTo = explicitWidth;
}
else
{
_widthTo = propertyChanges.end["width"];
}
}
else
{
_widthTo = (!isNaN(widthBy)) ?
widthFrom + widthBy :
target.width;
}
}
// Ditto for heightFrom, heightTo, and heightBy.
if (isNaN(heightFrom))
{
if (!isNaN(heightTo) && !isNaN(heightBy))
heightFrom = heightTo - heightBy;
else if (propertyChanges && propertyChanges.start["height"] != undefined)
heightFrom = propertyChanges.start["height"];
else
heightFrom = target.height;
}
if (isNaN(heightTo))
{
if (isNaN(heightBy) &&
propertyChanges &&
(propertyChanges.end["height"] !== undefined ||
explicitHeight !== undefined))
{
if (explicitHeight !== undefined && !isNaN(explicitHeight))
{
explicitHeightSet = true;
_heightTo = explicitHeight;
}
else
{
_heightTo = propertyChanges.end["height"];
}
}
else
{
_heightTo = (!isNaN(heightBy))?
heightFrom + heightBy :
target.height;
}
}
}
/**
* @private
*/
private function restorePanelChildren():void
{
if (hideChildrenTargets)
{
var n:int = hideChildrenTargets.length;
for (var i:int = 0; i < n; i++)
{
var p:IUIComponent = hideChildrenTargets[i];
var childArray:Array = restoreVisibleArray[i];
if (childArray)
{
var m:int = childArray.length;
for (var j:int = 0; j < m; j++)
{
childArray[j].setVisible(true, true);
}
}
if (restoreAutoLayoutArray[i])
Container(p).autoLayout = true;
// Trigger the resizeEndEffect (if any)
p.dispatchEvent(new Event("resizeEnd"));
}
}
}
//--------------------------------------------------------------------------
//
// Overridden event handlers
//
//--------------------------------------------------------------------------
/**
* @private
* This function is called when one of the Panels finishes
* its "hide children" animation.
*/
override mx_internal function eventHandler(event:Event):void
{
var panel:Container = event.target as Container;
super.eventHandler(event);
if (event.type == EffectEvent.EFFECT_START)
{
// Call my eventHandler() method when the effect finishes playing.
panel.addEventListener(EffectEvent.EFFECT_END, eventHandler);
// Remember how many effects we're waiting for
numHideEffectsPlaying++;
}
else if (event.type == EffectEvent.EFFECT_END)
{
// Remove the event listener that triggered this callback.
panel.removeEventListener(EffectEvent.EFFECT_END, eventHandler);
// Get the array index of the panel
var n:int = hideChildrenTargets.length;
for (var i:int = 0; i < n; i++)
{
if (hideChildrenTargets[i] == panel)
break;
}
makePanelChildrenInvisible(panel, i);
// If all panels have finished their "hide children" effect,
// then it's time to start our Resize effect.
if (--numHideEffectsPlaying == 0)
startResizeTween();
}
}
}
}