/**
* VERSION: 2.54
* DATE: 2011-04-26
* AS3
* UPDATES AND DOCS AT: http://www.greensock.com/autofitarea/
**/
package com.greensock.layout {
import flash.display.BitmapData;
import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.display.Graphics;
import flash.display.Shape;
import flash.events.Event;
import flash.geom.ColorTransform;
import flash.geom.Matrix;
import flash.geom.Rectangle;
/**
* AutoFitArea allows you to define a rectangular area and then attach()
DisplayObjects
* so that they automatically fill the area, scaling/stretching in any of the following modes: STRETCH,
* PROPORTIONAL_INSIDE, PROPORTIONAL_OUTSIDE, NONE, WIDTH_ONLY,
or HEIGHT_ONLY
. Horizontally
* align the attached DisplayObjects left, center, or right. Vertically align them top, center, or bottom.
* AutoFitArea extends the Shape
class, so you can alter the width/height/scaleX/scaleY/x/y
* properties of the AutoFitArea and then all of the attached objects will automatically be affected.
* Attach as many DisplayObjects as you want. To make visualization easy, you can set the previewColor
* to any color and set the preview
property to true in order to see the area on the stage
* (or simply use it like a regular Shape by adding it to the display list with addChild()
, but the
* preview
property makes it simpler because it automatically ensures that it is behind
* all of its attached DisplayObjects in the stacking order).
*
*
* When you attach()
a DisplayObject, you can define a minimum and maximum width and height.
* AutoFitArea doesn't require that the DisplayObject's registration point be in its upper left corner
* either. You can even set the calculateVisible
parameter to true when attaching an object
* so that AutoFitArea will ignore masked areas inside the DisplayObject (this is more processor-intensive,
* so beware).
*
* For scaling, AutoFitArea alters the DisplayObject's width
and/or height
* properties unless it is rotated in which case it alters the DisplayObject's transform.matrix
* directly so that accurate stretching/skewing can be accomplished.
*
* There is also a LiquidArea
class that extends AutoFitArea and integrates with
* LiquidStage so that it automatically
* adjusts its size whenever the stage is resized. This makes it simple to create things like
* a background that proportionally fills the stage or a bar that always stretches horizontally
* to fill the stage but stays stuck to the bottom, etc.
*
* @example Example AS3 code:
createAround(mc, {scaleMode:"proportionalOutside", crop:true});
instead of createAround(mc, "proportionalOutside", "center", "center", true, 0, 99999999, 0, 99999999, false, NaN, false);
.
* The following optional parameters are recognized:
* STRETCH, PROPORTIONAL_INSIDE, PROPORTIONAL_OUTSIDE, NONE, WIDTH_ONLY,
or HEIGHT_ONLY
LEFT
, CENTER
, and RIGHT
.TOP
, CENTER
, and BOTTOM
.roundPosition
to true
(it is false
by default).target
. For example, maybe the target contains 3 boxes arranged next to each other, left-to-right and instead of fitting ALL of those boxes into the area, you only want the center one fit into the area. In this case, you can define the customBoundsTarget as that center box so that the AutoFitArea/LiquidArea only uses it when calculating bounds. Make sure that the object is in the display list (its visible
property can be set to false if you want to use an invisible object to define custom bounds).calculateVisible
to true
degrades performance, so only use it when absolutely necessary.scaleMode
to PROPORTIONAL_INSIDE
or PROPORTIONAL_OUTSIDE
, its native (unscaled) dimensions will be used to determine the proportions (aspect ratio), but if you prefer to define a custom width-to-height ratio, use customAspectRatio
. For example, if an item is 100 pixels wide and 50 pixels tall at its native size, the aspect ratio would be 100/50 or 2. If, however, you want it to be square (a 1-to-1 ratio), the customAspectRatio
would be 1. visible
property to true (it is false by default).STRETCH, PROPORTIONAL_INSIDE, PROPORTIONAL_OUTSIDE, NONE, WIDTH_ONLY,
* or HEIGHT_ONLY
. Horizontally and vertically align the object within the area as well.
* When the area resizes, all attached DisplayObjects will automatically be moved/scaled accordingly.
*
* @param target The DisplayObject to attach and scale/stretch to fit within the area.
* @param vars An object used for defining various optional parameters (see below for list) - this is more readable and concise than defining 11 or more normal arguments.
* For example, attach(mc, {scaleMode:"proportionalOutside", crop:true});
instead of attach(mc, "proportionalOutside", "center", "center", true, 0, 99999999, 0, 99999999, false, NaN, false);
.
* The following optional parameters are recognized:
* STRETCH, PROPORTIONAL_INSIDE, PROPORTIONAL_OUTSIDE, NONE, WIDTH_ONLY,
or HEIGHT_ONLY
LEFT
, CENTER
, and RIGHT
.TOP
, CENTER
, and BOTTOM
.roundPosition
to true
(it is false
by default).target
. For example, maybe the target contains 3 boxes arranged next to each other, left-to-right and instead of fitting ALL of those boxes into the area, you only want the center one fit into the area. In this case, you can define the customBoundsTarget as that center box so that the AutoFitArea/LiquidArea only uses it when calculating bounds. Make sure that the object is in the display list (its visible
property can be set to false if you want to use an invisible object to define custom bounds).calculateVisible
to true
degrades performance, so only use it when absolutely necessary.scaleMode
to PROPORTIONAL_INSIDE
or PROPORTIONAL_OUTSIDE
, its native (unscaled) dimensions will be used to determine the proportions (aspect ratio), but if you prefer to define a custom width-to-height ratio, use customAspectRatio
. For example, if an item is 100 pixels wide and 50 pixels tall at its native size, the aspect ratio would be 100/50 or 2. If, however, you want it to be square (a 1-to-1 ratio), the customAspectRatio
would be 1. x, y, scaleX, scaleY, width,
or height
will force an immediate
* update()
call but when the area is in tween mode, that automatic update()
* is suspended. This effects perfomance because if, for example, you tween the area's x, y, width
,
* and height
properties simultaneously, update()
would get called 4 times
* each frame (once for each property) even though it only really needs to be called once after all
* properties were updated inside the tween. So to maximize performance during a tween, it is best
* to use the tween's onStart
to call enableTweenMode()
at the beginning
* of the tween, use the tween's onUpdate
to call the area's update()
method,
* and then the tween's onComplete
to call disableTweenMode()
like so:
*
* TweenLite.to(myArea, 3, {x:100, y:50, width:300, height:250, onStart:myArea.enableTweenMode, onUpdate:myArea.update, onComplete:myArea.disableTweenMode});
**/
public function enableTweenMode():void {
_tweenMode = true;
}
/**
* Disables the area's tween mode; normally, any changes to the area's transform properties like
* x, y, scaleX, scaleY, width,
or height
will force an immediate
* update()
call but when the area is in tween mode, that automatic update()
* is suspended. This effects perfomance because if, for example, you tween the area's x, y, width
,
* and height
properties simultaneously, update()
would get called 4 times
* each frame (once for each property) even though it only really needs to be called once after all
* properties were updated inside the tween. So to maximize performance during a tween, it is best
* to use the tween's onStart
to call enableTweenMode()
at the beginning
* of the tween, use the tween's onUpdate
to call the area's update()
method,
* and then the tween's onComplete
to call disableTweenMode()
like so:
*
* TweenLite.to(myArea, 3, {x:100, y:50, width:300, height:250, onStart:myArea.enableTweenMode, onUpdate:myArea.update, onComplete:myArea.disableTweenMode});
**/
public function disableTweenMode():void {
_tweenMode = false;
}
/**
* Allows you to add an Event.CHANGE
event listener.
*
* @param type Event type (Event.CHANGE
)
* @param listener Listener function
* @param useCapture useCapture
* @param priority Priority level
* @param useWeakReference Use weak references
*/
override public function addEventListener(type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false):void {
_hasListener = true;
super.addEventListener(type, listener, useCapture, priority, useWeakReference);
}
/** Destroys the instance by releasing all DisplayObjects, setting preview to false, and nulling references to the parent, ensuring that garbage collection isn't hindered. **/
public function destroy():void {
if (_preview) {
this.preview = false;
}
var nxt:AutoFitItem;
var item:AutoFitItem = _rootItem;
while (item) {
nxt = item.next;
release(item.target);
item = nxt;
}
if (_bd != null) {
_bd.dispose();
_bd = null;
}
_parent = null;
}
/** @private For objects with masks, the only way to accurately report the bounds of the visible areas is to use BitmapData. **/
protected static function getVisibleBounds(target:DisplayObject, targetCoordinateSpace:DisplayObject):Rectangle {
if (_bd == null) {
_bd = new BitmapData(2800, 2800, true, 0x00FFFFFF);
}
var msk:DisplayObject = target.mask;
target.mask = null;
_bd.fillRect(_rect, 0x00FFFFFF);
_matrix.tx = _matrix.ty = 0;
var offset:Rectangle = target.getBounds(targetCoordinateSpace);
var m:Matrix = (targetCoordinateSpace == target) ? _matrix : target.transform.matrix;
m.tx -= offset.x;
m.ty -= offset.y;
_bd.draw(target, m, null, "normal", _rect, false);
var bounds:Rectangle = _bd.getColorBoundsRect(0xFF000000, 0x00000000, false);
bounds.x += offset.x;
bounds.y += offset.y;
target.mask = msk;
return bounds;
}
/** @private **/
protected function _redraw(color:uint):void {
_previewColor = color;
var g:Graphics = this.graphics;
g.clear();
g.beginFill(_previewColor, 1);
g.drawRect(0, 0, _width, _height);
g.endFill();
}
//---- GETTERS / SETTERS ---------------------------------------------------------------------------
/** @inheritDoc **/
override public function set x(value:Number):void {
super.x = value;
if (!_tweenMode) {
update();
}
}
/** @inheritDoc **/
override public function set y(value:Number):void {
super.y = value;
if (!_tweenMode) {
update();
}
}
/** @inheritDoc **/
override public function set width(value:Number):void {
super.width = value;
if (!_tweenMode) {
update();
}
}
/** @inheritDoc **/
override public function set height(value:Number):void {
super.height = value;
if (!_tweenMode) {
update();
}
}
/** @inheritDoc **/
override public function set scaleX(value:Number):void {
super.scaleX = value;
update();
}
/** @inheritDoc **/
override public function set scaleY(value:Number):void {
super.scaleY = value;
update();
}
/** @inheritDoc **/
override public function set rotation(value:Number):void {
trace("Warning: AutoFitArea instances should not be rotated.");
}
/** The preview color with which the area should be filled, making it easy to visualize on the stage. You will not see this color unless you set preview
to true or manually add the area to the display list with addChild(). **/
public function get previewColor():uint {
return _previewColor;
}
public function set previewColor(value:uint):void {
_redraw(value);
}
/** To see a visual representation of the area on the screen, set preview
to true
. Doing so will add the area to the display list behind any DisplayObjects that have been attached. **/
public function get preview():Boolean {
return _preview;
}
public function set preview(value:Boolean):void {
_preview = value;
if (this.parent == _parent) {
_parent.removeChild(this);
}
if (value) {
var level:uint = (_rootItem == null) ? 0 : 999999999;
var index:uint;
var item:AutoFitItem = _rootItem;
while (item) {
if (item.target.parent == _parent) {
index = _parent.getChildIndex(item.target);
if (index < level) {
level = index;
}
}
item = item.next;
}
_parent.addChildAt(this, level);
this.visible = true;
}
}
}
}
import flash.display.BitmapData;
import flash.display.DisplayObject;
import flash.display.Shape;
import flash.geom.Matrix;
import flash.geom.Rectangle;
internal class AutoFitItem {
public var target:DisplayObject;
public var scaleMode:String;
public var hAlign:String;
public var vAlign:String;
public var minWidth:Number;
public var maxWidth:Number;
public var minHeight:Number;
public var maxHeight:Number;
public var aspectRatio:Number;
public var mask:Shape;
public var matrix:Matrix;
public var hasCustomRatio:Boolean;
public var roundPosition:Boolean;
public var next:AutoFitItem;
public var prev:AutoFitItem;
public var calculateVisible:Boolean;
public var boundsTarget:DisplayObject;
public var bounds:Rectangle;
public var hasDrawNow:Boolean;
/** @private **/
public function AutoFitItem(target:DisplayObject, vars:Object, next:AutoFitItem) {
this.target = target;
if (vars == null) {
vars = {};
}
this.scaleMode = vars.scaleMode || "proportionalInside";
this.hAlign = vars.hAlign || "center";
this.vAlign = vars.vAlign || "center";
this.minWidth = Number(vars.minWidth) || 0;
this.maxWidth = isNaN(vars.maxWidth) ? 999999999 : Number(vars.maxWidth);
this.minHeight = Number(vars.minHeight) || 0;
this.maxHeight = isNaN(vars.maxHeight) ? 999999999 : Number(vars.maxHeight);
this.roundPosition = Boolean(vars.roundPosition);
this.boundsTarget = (vars.customBoundsTarget is DisplayObject) ? vars.customBoundsTarget : this.target;
this.matrix = target.transform.matrix;
this.calculateVisible = Boolean(vars.calculateVisible);
this.hasDrawNow = this.target.hasOwnProperty("drawNow");
if (this.hasDrawNow) {
Object(this.target).drawNow(); //just to make sure we're starting with the correct values if it's a component.
}
if (!isNaN(vars.customAspectRatio)) {
this.aspectRatio = vars.customAspectRatio;
this.hasCustomRatio = true;
}
if (next) {
next.prev = this;
this.next = next;
}
}
/** @private **/
public function setVisibleWidth(value:Number):void {
var m:Matrix = this.target.transform.matrix;
if ((m.a == 0 && m.c == 0) || (m.d == 0 && m.b == 0)) {
m.a = this.matrix.a;
m.c = this.matrix.c;
}
var curWidth:Number = (m.a < 0) ? -m.a * this.bounds.width : m.a * this.bounds.width;
curWidth += (m.c < 0) ? -m.c * this.bounds.height : m.c * this.bounds.height;
if (curWidth != 0) {
var scale:Number = value / curWidth;
m.a *= scale;
m.c *= scale;
this.target.transform.matrix = m;
if (value != 0) {
this.matrix = m;
}
}
}
/** @private **/
public function setVisibleHeight(value:Number):void {
var m:Matrix = this.target.transform.matrix;
if ((m.a == 0 && m.c == 0) || (m.d == 0 && m.b == 0)) {
m.b = this.matrix.b;
m.d = this.matrix.d;
}
var curHeight:Number = (m.b < 0) ? -m.b * this.bounds.width : m.b * this.bounds.width;
curHeight += (m.d < 0) ? -m.d * this.bounds.height : m.d * this.bounds.height;
if (curHeight != 0) {
var scale:Number = value / curHeight;
m.b *= scale;
m.d *= scale;
this.target.transform.matrix = m;
if (value != 0) {
this.matrix = m;
}
}
}
/** @private **/
public function scaleVisibleWidth(value:Number):void {
var m:Matrix = this.target.transform.matrix;
m.a *= value;
m.c *= value;
this.target.transform.matrix = m;
if (value != 0) {
this.matrix = m;
}
}
/** @private **/
public function scaleVisibleHeight(value:Number):void {
var m:Matrix = this.target.transform.matrix;
m.b *= value;
m.d *= value;
this.target.transform.matrix = m;
if (value != 0) {
this.matrix = m;
}
}
}