blob: 9507e5a5c08a162ea390434c542ff98f531bcb74 [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.core
{
import flash.events.Event;
import flash.geom.Matrix;
import flash.geom.Matrix3D;
import flash.geom.Point;
import flash.geom.Vector3D;
import flash.system.Capabilities;
import mx.geom.CompoundTransform;
import mx.geom.TransformOffsets;
use namespace mx_internal;
/**
* @private
* Transform Offsets can be assigned to any Component or GraphicElement to modify the transform
* of the object beyond where its parent layout places it.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public class AdvancedLayoutFeatures implements IAssetLayoutFeatures
{
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function AdvancedLayoutFeatures()
{
layout = new CompoundTransform();
}
/**
* @private
* a flag for use by the owning object indicating whether the owning object has a pending update
* to the computed matrix. it is the owner's responsibility to set this flag.
*/
public var updatePending:Boolean = false;
/**
* storage for the depth value. Layering is considered 'advanced' layout behavior, and not something
* that gets used by the majority of the components out there. So if a component has a non-zero depth,
* it will allocate a AdvancedLayoutFeatures object and store the value here.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var depth:Number = 0;
/**
* @private
* slots for the various 2D and 3D matrices for layout, offset, and computed transforms. Note that
* these are only allocated and computed on demand -- many component instances will never use a 3D
* matrix, for example.
*/
protected var _computedMatrix:Matrix;
protected var _computedMatrix3D:Matrix3D;
/**
* @private
* the layout visible transform as defined by the user and parent layout.
*/
protected var layout:CompoundTransform;
/**
* @private
* offset values applied by the user
*/
private var _postLayoutTransformOffsets:TransformOffsets;
/**
* @private
* bit field flags for indicating which transforms are valid -- the layout properties, the matrices,
* and the 3D matrices. Since developers can set any of the three programmatically, the last one set
* will always be valid, and the others will be invalid until validated on demand.
*/
private static const COMPUTED_MATRIX_VALID:uint = 0x1;
private static const COMPUTED_MATRIX3D_VALID:uint = 0x2;
/**
* @private
* general storage for all of our flags.
*/
private var _flags:uint = 0;
/**
* @private
* static data used by utility methods below
*/
private static var reVT:Vector3D = new Vector3D(0,0,0);
private static var reVR:Vector3D = new Vector3D(0,0,0);
private static var reVS:Vector3D = new Vector3D(1,1,1);
private static var reV:Vector.<Vector3D> = new Vector.<Vector3D>();
reV.push(reVT);
reV.push(reVR);
reV.push(reVS);
private static const RADIANS_PER_DEGREES:Number = Math.PI / 180;
private static const ZERO_REPLACEMENT_IN_3D:Number = .00000000000001;
private static var tempLocalPosition:Vector3D;
/**
* @private
* a pointer to the function we use to transform vectors, to work around a bug
* in early versions of the flash player.
*/
private static var transformVector:Function = initTransformVectorFunction;
/**
* @private
* an actionscript implementation to transform a vector by a matrix. Bugs in early versions of
* flash 10's implementation of Matrix.transformVector force us to do it ourselves in actionscript.
*/
private static function pre10_0_22_87_transformVector(m:Matrix3D,v:Vector3D):Vector3D
{
var r:Vector.<Number> = m.rawData;
return new Vector3D(
r[0] * v.x + r[4] * v.y + r[8] * v.z + r[12],
r[1] * v.x + r[5] * v.y + r[9] * v.z + r[13],
r[2] * v.x + r[6] * v.y + r[10] * v.z + r[14],
1);
}
/**
* @private
* a function to transform vectors using the built in player API, if we're in a late enough player version
* that we won't run into bugs.s
*/
private static function nativeTransformVector(m:Matrix3D,v:Vector3D):Vector3D
{
return m.transformVector(v);
}
/**
* @private
* the first time someone calls transformVector, they'll get this function. It checks the player version,
* and if decides which implementation to use based on whether a bug is present or not.
*/
private static function initTransformVectorFunction(m:Matrix3D,v:Vector3D):Vector3D
{
var canUseNative:Boolean = false;
var version:Array = Capabilities.version.split(' ')[1].split(',');
if (parseFloat(version[0]) > 10)
canUseNative = true;
else if (parseFloat(version[1]) > 0)
canUseNative = true;
else if (parseFloat(version[2]) > 22)
canUseNative = true;
else if (parseFloat(version[3]) >= 87)
canUseNative = true;
if (canUseNative)
transformVector = nativeTransformVector;
else
transformVector = pre10_0_22_87_transformVector;
return transformVector(m,v);
}
//------------------------------------------------------------------------------
/**
* layout transform convenience property. Represents the x value of the layout matrix used in layout and in
* the computed transform.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function set layoutX(value:Number):void
{
layout.x = value;
invalidate();
}
/**
* @private
*/
public function get layoutX():Number
{
return layout.x;
}
/**
* layout transform convenience property. Represents the y value of the layout matrix used in layout and in
* the computed transform.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function set layoutY(value:Number):void
{
layout.y = value;
invalidate();
}
/**
* @private
*/
public function get layoutY():Number
{
return layout.y;
}
/**
* layout transform convenience property. Represents the z value of the layout matrix used in layout and in
* the computed transform.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function set layoutZ(value:Number):void
{
layout.z = value;
invalidate();
}
/**
* @private
*/
public function get layoutZ():Number
{
return layout.z;
}
//----------------------------------
// layoutWidth
//----------------------------------
private var _layoutWidth:Number = 0;
/**
* Used by the mirroring transform. See the mirror property.
* @default 0
*/
public function get layoutWidth():Number
{
return _layoutWidth;
}
/**
* @private
*/
public function set layoutWidth(value:Number):void
{
if (value == _layoutWidth)
return;
_layoutWidth = value;
invalidate();
}
//------------------------------------------------------------------------------
/**
* @private
* the x value of the point around which any rotation and scale is performed in both the layout and computed matrix.
*/
public function set transformX(value:Number):void
{
layout.transformX = value;
invalidate();
}
/**
* @private
*/
public function get transformX():Number
{
return layout.transformX;
}
/**
* @private
* the y value of the point around which any rotation and scale is performed in both the layout and computed matrix.
*/
public function set transformY(value:Number):void
{
layout.transformY = value;
invalidate();
}
/**
* @private
*/
public function get transformY():Number
{
return layout.transformY;
}
/**
* @private
* the z value of the point around which any rotation and scale is performed in both the layout and computed matrix.
*/
public function set transformZ(value:Number):void
{
layout.transformZ = value;
invalidate();
}
/**
* @private
*/
public function get transformZ():Number
{
return layout.transformZ;
}
//------------------------------------------------------------------------------
/**
* layout transform convenience property. Represents the rotation around the X axis of the layout matrix used in layout and in
* the computed transform.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function set layoutRotationX(value:Number):void
{
layout.rotationX= value;
invalidate();
}
/**
* @private
*/
public function get layoutRotationX():Number
{
return layout.rotationX;
}
/**
* layout transform convenience property. Represents the rotation around the Y axis of the layout matrix used in layout and in
* the computed transform.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function set layoutRotationY(value:Number):void
{
layout.rotationY= value;
invalidate();
}
/**
* @private
*/
public function get layoutRotationY():Number
{
return layout.rotationY;
}
/**
* layout transform convenience property. Represents the rotation around the Z axis of the layout matrix used in layout and in
* the computed transform.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function set layoutRotationZ(value:Number):void
{
layout.rotationZ= value;
invalidate();
}
/**
* @private
*/
public function get layoutRotationZ():Number
{
return layout.rotationZ;
}
//------------------------------------------------------------------------------
/**
* layout transform convenience property. Represents the scale along the X axis of the layout matrix used in layout and in
* the computed transform.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function set layoutScaleX(value:Number):void
{
layout.scaleX = value;
invalidate();
}
/**
* @private
*/
public function get layoutScaleX():Number
{
return layout.scaleX;
}
/**
* layout transform convenience property. Represents the scale along the Y axis of the layout matrix used in layout and in
* the computed transform.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function set layoutScaleY(value:Number):void
{
layout.scaleY= value;
invalidate();
}
/**
* @private
*/
public function get layoutScaleY():Number
{
return layout.scaleY;
}
/**
* layout transform convenience property. Represents the scale along the Z axis of the layout matrix used in layout and in
* the computed transform.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function set layoutScaleZ(value:Number):void
{
layout.scaleZ= value;
invalidate();
}
/**
* @private
*/
public function get layoutScaleZ():Number
{
return layout.scaleZ;
}
/**
* @private
* The 2D matrix used during layout calculations to determine the layout and size of the component and its parent and siblings.
* If the convenience properties are set, this matrix is built from those properties.
* If the matrix is set directly, the convenience properties will be updated to values derived from this matrix.
* This matrix is used in the calculation of the computed transform if possible. Under certain circumstances, such as when
* offsets are provided, the decomposed layout properties will be used instead.
*/
public function set layoutMatrix(value:Matrix):void
{
layout.matrix = value;
invalidate();
}
/**
* @private
*/
public function get layoutMatrix():Matrix
{
return layout.matrix;
}
/**
* @private
* The 3D matrix used during layout calculations to determine the layout and size of the component and its parent and siblings.
* This matrix is only used by parents that respect 3D layoyut.
* If the convenience properties are set, this matrix is built from those properties.
* If the matrix is set directly, the convenience properties will be updated to values derived from this matrix.
* This matrix is used in the calculation of the computed transform if possible. Under certain circumstances, such as when
* offsets are provided, the decomposed layout properties will be used instead.
*/
public function set layoutMatrix3D(value:Matrix3D):void
{
layout.matrix3D = value;
invalidate();
}
/**
* @private
*/
public function get layoutMatrix3D():Matrix3D
{
return layout.matrix3D;
}
/**
* true if the computed transform has 3D values.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get is3D():Boolean
{
return (layout.is3D || (postLayoutTransformOffsets != null && postLayoutTransformOffsets.is3D));
}
/**
* true if the layout transform has 3D values.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get layoutIs3D():Boolean
{
return layout.is3D;
}
//------------------------------------------------------------------------------
/** offsets to the transform convenience properties that are applied when a component is rendered. If this
* property is set, its values will be added to the layout transform properties to determine the true matrix used to render
* the component
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function set postLayoutTransformOffsets(value:TransformOffsets):void
{
if (_postLayoutTransformOffsets != null)
{
_postLayoutTransformOffsets.removeEventListener(Event.CHANGE,postLayoutTransformOffsetsChangedHandler);
_postLayoutTransformOffsets.owner = null;
}
_postLayoutTransformOffsets = value;
if (_postLayoutTransformOffsets != null)
{
_postLayoutTransformOffsets.addEventListener(Event.CHANGE,postLayoutTransformOffsetsChangedHandler);
_postLayoutTransformOffsets.owner = this;
}
invalidate();
}
public function get postLayoutTransformOffsets():TransformOffsets
{
return _postLayoutTransformOffsets;
}
private function postLayoutTransformOffsetsChangedHandler(e:Event):void
{
invalidate();
}
//----------------------------------
// mirror
//----------------------------------
private var _mirror:Boolean = false;
/**
* If true the X axis is scaled by -1 and the x coordinate of the origin
* is translated by the component's width.
*
* The net effect of this "mirror" transform is to flip the direction
* that the X axis increases in without changing the layout element's
* location relative to the parent's origin.
*
* @default false
*/
public function get mirror():Boolean
{
return _mirror;
}
/**
* @private
*/
public function set mirror(value:Boolean):void
{
_mirror = value;
invalidate();
}
//----------------------------------
// stretchX
//----------------------------------
private var _stretchX:Number = 1;
/**
* The stretchY is the horizontal component of the stretch scale factor which
* is applied before any other transformation property.
* @default 1
*/
public function get stretchX():Number
{
return _stretchX;
}
/**
* @private
*/
public function set stretchX(value:Number):void
{
if (value == _stretchX)
return;
_stretchX = value;
invalidate();
}
//----------------------------------
// stretchY
//----------------------------------
private var _stretchY:Number = 1;
/**
* The stretchY is the vertical component of the stretch scale factor which
* is applied before any other transformation property.
* @default 1
*/
public function get stretchY():Number
{
return _stretchY;
}
/**
* @private
*/
public function set stretchY(value:Number):void
{
if (value == _stretchY)
return;
_stretchY = value;
invalidate();
}
//------------------------------------------------------------------------------
/**
* @private
* invalidates our various cached values. Any change to the AdvancedLayoutFeatures object that affects
* the various transforms should call this function.
* @param reason - the code indicating what changes to cause the invalidation.
* @param affects3D - a flag indicating whether the change affects the 2D/3D nature of the various transforms.
* @param dispatchChangeEvent - if true, the AdvancedLayoutFeatures will dispatch a change indicating that its underlying transforms
* have been modified.
*/
private function invalidate():void
{
_flags &= ~COMPUTED_MATRIX_VALID;
_flags &= ~COMPUTED_MATRIX3D_VALID;
}
/**
* the computed matrix, calculated by combining the layout matrix and and any offsets provided..
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get computedMatrix():Matrix
{
if (_flags & COMPUTED_MATRIX_VALID)
return _computedMatrix;
if (!postLayoutTransformOffsets && !mirror && stretchX == 1 && stretchY == 1)
{
return layout.matrix;
}
var m:Matrix = _computedMatrix;
if (m == null)
m = _computedMatrix = new Matrix();
else
m.identity();
var tx:Number = layout.transformX;
var ty:Number = layout.transformY;
var sx:Number = layout.scaleX;
var sy:Number = layout.scaleY;
var rz:Number = layout.rotationZ;
var x:Number = layout.x;
var y:Number = layout.y;
if (mirror)
{
sx *= -1;
x += layoutWidth;
}
if (postLayoutTransformOffsets)
{
sx *= postLayoutTransformOffsets.scaleX;
sy *= postLayoutTransformOffsets.scaleY;
rz += postLayoutTransformOffsets.rotationZ;
x += postLayoutTransformOffsets.x;
y += postLayoutTransformOffsets.y;
}
if (stretchX != 1 || stretchY != 1)
m.scale(stretchX, stretchY);
build2DMatrix(m, tx, ty, sx, sy, rz, x, y);
_flags |= COMPUTED_MATRIX_VALID;
return m;
}
/**
* the computed 3D matrix, calculated by combining the 3D layout matrix and and any offsets provided..
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get computedMatrix3D():Matrix3D
{
if (_flags & COMPUTED_MATRIX3D_VALID)
return _computedMatrix3D;
if (!postLayoutTransformOffsets && !mirror && stretchX == 1 && stretchY == 1)
{
return layout.matrix3D;
}
var m:Matrix3D = _computedMatrix3D;
if (m == null)
m = _computedMatrix3D = new Matrix3D();
else
m.identity();
var tx:Number = layout.transformX;
var ty:Number = layout.transformY;
var tz:Number = layout.transformZ;
var sx:Number = layout.scaleX;
var sy:Number = layout.scaleY;
var sz:Number = layout.scaleZ;
var rx:Number = layout.rotationX;
var ry:Number = layout.rotationY;
var rz:Number = layout.rotationZ;
var x:Number = layout.x;
var y:Number = layout.y;
var z:Number = layout.z;
if (mirror)
{
sx *= -1;
x += layoutWidth;
}
if (postLayoutTransformOffsets)
{
sx *= postLayoutTransformOffsets.scaleX;
sy *= postLayoutTransformOffsets.scaleY;
sz *= postLayoutTransformOffsets.scaleZ;
rx += postLayoutTransformOffsets.rotationX;
ry += postLayoutTransformOffsets.rotationY;
rz += postLayoutTransformOffsets.rotationZ;
x += postLayoutTransformOffsets.x;
y += postLayoutTransformOffsets.y;
z += postLayoutTransformOffsets.z;
}
build3DMatrix(m, tx, ty, tz, sx, sy, sz, rx, ry, rz, x, y, z);
// Always prepend last
if (stretchX != 1 || stretchY != 1)
m.prependScale(stretchX, stretchY, 1);
_flags |= COMPUTED_MATRIX3D_VALID;
return m;
}
/**
* @private
* convenience function for building a 2D matrix from the convenience properties
*/
public static function build2DMatrix(m:Matrix,
tx:Number,ty:Number,
sx:Number,sy:Number,
rz:Number,
x:Number,y:Number):void
{
m.translate(-tx,-ty);
m.scale(sx,sy);
m.rotate(rz* RADIANS_PER_DEGREES);
m.translate(x+tx,y+ty);
}
/**
* @private
* convenience function for building a 3D matrix from the convenience properties
*/
public static function build3DMatrix(m:Matrix3D,
tx:Number,ty:Number,tz:Number,
sx:Number,sy:Number,sz:Number,
rx:Number,ry:Number,rz:Number,
x:Number,y:Number,z:Number):void
{
reVR.x = rx * RADIANS_PER_DEGREES;
reVR.y = ry * RADIANS_PER_DEGREES;
reVR.z = rz * RADIANS_PER_DEGREES;
m.recompose(reV);
if (sx == 0)
sx = ZERO_REPLACEMENT_IN_3D;
if (sy == 0)
sy = ZERO_REPLACEMENT_IN_3D;
if (sz == 0)
sz = ZERO_REPLACEMENT_IN_3D;
m.prependScale(sx,sy,sz);
m.prependTranslation(-tx,-ty,-tz);
m.appendTranslation(tx+x,ty+y,tz+z);
}
/**
* A utility method to transform a point specified in the local
* coordinates of this object to its location in the object's parent's
* coordinates. The pre-layout and post-layout result will be set on
* the <code>position</code> and <code>postLayoutPosition</code>
* parameters, if they are non-null.
*
* @param propertyIs3D A boolean reflecting whether the calculation needs
* to take into account the 3D matrix of the object.
* @param localPoint The point to be transformed, specified in the
* local coordinates of the object.
* @position A Vector3D point that will hold the pre-layout
* result. If null, the parameter is ignored.
* @postLayoutPosition A Vector3D point that will hold the post-layout
* result. If null, the parameter is ignored.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function transformPointToParent(propertyIs3D:Boolean,
localPosition:Vector3D, position:Vector3D,
postLayoutPosition:Vector3D):void
{
var transformedV:Vector3D;
var transformedP:Point;
tempLocalPosition =
localPosition ?
localPosition.clone() :
new Vector3D();
if (is3D || propertyIs3D)
{
if (position != null)
{
transformedV = transformVector(layoutMatrix3D, tempLocalPosition);
position.x = transformedV.x;
position.y = transformedV.y;
position.z = transformedV.z;
}
if (postLayoutPosition != null)
{
// computedMatrix factor in stretchXY, so divide it out of position first
tempLocalPosition.x /= stretchX;
tempLocalPosition.y /= stretchY;
transformedV = transformVector(computedMatrix3D, tempLocalPosition);
postLayoutPosition.x = transformedV.x;
postLayoutPosition.y = transformedV.y;
postLayoutPosition.z = transformedV.z;
}
}
else
{
var localP:Point = new Point(tempLocalPosition.x,
tempLocalPosition.y);
if (position != null)
{
transformedP = layoutMatrix.transformPoint(localP);
position.x = transformedP.x;
position.y = transformedP.y;
position.z = 0;
}
if (postLayoutPosition != null)
{
// computedMatrix factor in stretchXY, so divide it out of position first
localP.x /= stretchX;
localP.y /= stretchY;
transformedP = computedMatrix.transformPoint(localP);
postLayoutPosition.x = transformedP.x;
postLayoutPosition.y = transformedP.y;
postLayoutPosition.z = 0;
}
}
}
/**
* @private
* call when you've changed the inputs to the computed transform to make
* any adjustments to keep a particular point fixed in parent coordinates.
*/
private function completeTransformCenterAdjustment(changeIs3D:Boolean,
transformCenter:Vector3D, targetPosition:Vector3D,
targetPostLayoutPosition:Vector3D):void
{
// TODO (chaase): optimize for transformCenter == (0,0,0)
if (is3D || changeIs3D)
{
if (targetPosition != null)
{
var adjustedLayoutCenterV:Vector3D = transformVector(layoutMatrix3D, transformCenter);
if (adjustedLayoutCenterV.equals(targetPosition) == false)
{
layout.translateBy(targetPosition.x - adjustedLayoutCenterV.x,
targetPosition.y - adjustedLayoutCenterV.y,
targetPosition.z - adjustedLayoutCenterV.z);
invalidate();
}
}
if (targetPostLayoutPosition != null && _postLayoutTransformOffsets != null)
{
// computedMatrix factor in stretchXY, so divide it out of transform center first
var tmpPos:Vector3D = new Vector3D(transformCenter.x, transformCenter.y, transformCenter.z);
tmpPos.x /= stretchX;
tmpPos.y /= stretchY;
var adjustedComputedCenterV:Vector3D = transformVector(computedMatrix3D, tmpPos);
if (adjustedComputedCenterV.equals(targetPostLayoutPosition) == false)
{
postLayoutTransformOffsets.x +=targetPostLayoutPosition.x - adjustedComputedCenterV.x;
postLayoutTransformOffsets.y += targetPostLayoutPosition.y - adjustedComputedCenterV.y;
postLayoutTransformOffsets.z += targetPostLayoutPosition.z - adjustedComputedCenterV.z;
invalidate();
}
}
}
else
{
var transformCenterP:Point = new Point(transformCenter.x,transformCenter.y);
if (targetPosition != null)
{
var currentPositionP:Point = layoutMatrix.transformPoint(transformCenterP);
if (currentPositionP.x != targetPosition.x ||
currentPositionP.y != targetPosition.y)
{
layout.translateBy(targetPosition.x - currentPositionP.x,
targetPosition.y - currentPositionP.y, 0);
invalidate();
}
}
if (targetPostLayoutPosition != null && _postLayoutTransformOffsets != null)
{
// computedMatrix factor in stretchXY, so divide it out of transform center first
transformCenterP.x /= stretchX;
transformCenterP.y /= stretchY;
var currentPostLayoutPosition:Point =
computedMatrix.transformPoint(transformCenterP);
if (currentPostLayoutPosition.x != targetPostLayoutPosition.x ||
currentPostLayoutPosition.y != targetPostLayoutPosition.y)
{
_postLayoutTransformOffsets.x += targetPostLayoutPosition.x - currentPostLayoutPosition.x;
_postLayoutTransformOffsets.y += targetPostLayoutPosition.y - currentPostLayoutPosition.y;
invalidate();
}
}
}
}
private static var staticTranslation:Vector3D = new Vector3D();
private static var staticOffsetTranslation:Vector3D = new Vector3D();
/**
* A utility method to update the rotation and scale of the transform
* while keeping a particular point, specified in the component's own
* coordinate space, fixed in the parent's coordinate space. This
* function will assign the rotation and scale values provided, then
* update the x/y/z properties as necessary to keep tx/ty/tz fixed.
* @param transformCenter the point, in the component's own coordinates,
* to keep fixed relative to its parent.
* @param rotation the new values for the rotation of the transform
* @param scale the new values for the scale of the transform
* @param translation the new values for the translation of the transform
*/
public function transformAround(transformCenter:Vector3D,
scale:Vector3D,
rotation:Vector3D,
transformCenterPosition:Vector3D,
postLayoutScale:Vector3D = null,
postLayoutRotation:Vector3D = null,
postLayoutTransformCenterPosition:Vector3D = null):void
{
var is3D:Boolean = (scale != null && scale.z != 1) ||
(rotation != null && ((rotation.x != 0 ) || (rotation.y != 0))) ||
(transformCenterPosition != null && transformCenterPosition.z != 0) ||
(postLayoutScale != null && postLayoutScale.z != 1) ||
(postLayoutRotation != null &&
(postLayoutRotation.x != 0 || postLayoutRotation.y != 0)) ||
(postLayoutTransformCenterPosition != null && postLayoutTransformCenterPosition.z != 0);
var needOffsets:Boolean = _postLayoutTransformOffsets == null &&
(postLayoutScale != null || postLayoutRotation != null ||
postLayoutTransformCenterPosition != null);
if (needOffsets)
_postLayoutTransformOffsets = new TransformOffsets();
// now if they gave us a non-trivial transform center, and didn't tell us where they want it,
// we need to calculate where it is so that we can make sure we keep it there.
if (transformCenter != null &&
(transformCenterPosition == null || postLayoutTransformCenterPosition == null))
{
transformPointToParent(is3D, transformCenter, staticTranslation,
staticOffsetTranslation);
if (postLayoutTransformCenterPosition == null && transformCenterPosition != null)
{
staticOffsetTranslation.x = transformCenterPosition.x + staticOffsetTranslation.x - staticTranslation.x;
staticOffsetTranslation.y = transformCenterPosition.y + staticOffsetTranslation.y - staticTranslation.y;
staticOffsetTranslation.z = transformCenterPosition.z + staticOffsetTranslation.z - staticTranslation.z;
}
}
// if targetPosition/postLayoutTargetPosition is null here, it might be because the caller passed in
// requested values, so we haven't calculated it yet. So that means our target position is the values
// they passed in.
var targetPosition:Vector3D = (transformCenterPosition == null)? staticTranslation:transformCenterPosition;
var postLayoutTargetPosition:Vector3D = (postLayoutTransformCenterPosition == null)? staticOffsetTranslation:postLayoutTransformCenterPosition;
// now update our transform values.
if (rotation != null)
{
if (!isNaN(rotation.x))
layout.rotationX = rotation.x;
if (!isNaN(rotation.y))
layout.rotationY = rotation.y;
if (!isNaN(rotation.z))
layout.rotationZ = rotation.z;
}
if (scale != null)
{
if (!isNaN(scale.x))
layout.scaleX = scale.x;
if (!isNaN(scale.y))
layout.scaleY = scale.y;
if (!isNaN(scale.z))
layout.scaleZ = scale.z;
}
if (postLayoutRotation != null)
{
_postLayoutTransformOffsets.rotationX = postLayoutRotation.x;
_postLayoutTransformOffsets.rotationY = postLayoutRotation.y;
_postLayoutTransformOffsets.rotationZ = postLayoutRotation.z;
}
if (postLayoutScale != null)
{
_postLayoutTransformOffsets.scaleX = postLayoutScale.x;
_postLayoutTransformOffsets.scaleY = postLayoutScale.y;
_postLayoutTransformOffsets.scaleZ = postLayoutScale.z;
}
// if they didn't pass us a transform center,
// then we assume it's the origin. In that case, it's trivially easy
// to make sure the origin is at a particular point...we simply set
// the transformCenterPosition portion of our transforms to that point.
if (transformCenter == null)
{
if (transformCenterPosition != null)
{
layout.x = transformCenterPosition.x;
layout.y = transformCenterPosition.y;
layout.z = transformCenterPosition.z;
}
if (postLayoutTransformCenterPosition != null)
{
_postLayoutTransformOffsets.x = postLayoutTransformCenterPosition.x - layout.x;
_postLayoutTransformOffsets.y = postLayoutTransformCenterPosition.y - layout.y;
_postLayoutTransformOffsets.z = postLayoutTransformCenterPosition.z - layout.z;
}
}
invalidate();
// if they did pass in a transform center, go do the adjustments necessary to keep it fixed in place.
if (transformCenter != null)
completeTransformCenterAdjustment(is3D, transformCenter,
targetPosition, postLayoutTargetPosition);
}
}
}