blob: e0593e9d3592b8145fa9252c54bdffcba64acaa6 [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 spark.primitives
{
import flash.display.Graphics;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import mx.core.mx_internal;
import mx.utils.GraphicsUtil;
import mx.utils.MatrixUtil;
import spark.primitives.supportClasses.FilledElement;
use namespace mx_internal;
/**
* The Rect class is a filled graphic element that draws a rectangle.
* The corners of the rectangle can be rounded. The <code>drawElement()</code> method
* calls the <code>Graphics.drawRect()</code> and <code>Graphics.drawRoundRect()</code>
* methods.
*
* <p><b>Note: </b>By default, the stroke of the border is rounded.
* If you do not want rounded corners, set the <code>joints</code> property of
* the stroke to <code>JointStyle.MITER</code>. </p>
*
* @see flash.display.Graphics
*
* @includeExample examples/RectExample.mxml
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public class Rect extends FilledElement
{
include "../core/Version.as";
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function Rect()
{
super();
}
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// bottomLeftRadiusX
//----------------------------------
private var _bottomLeftRadiusX:Number;
[Inspectable(category="General", minValue="0.0")]
/**
* The x radius of the bottom left corner of the rectangle.
*
* @default NaN
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get bottomLeftRadiusX():Number
{
return _bottomLeftRadiusX;
}
public function set bottomLeftRadiusX(value:Number):void
{
if (value != _bottomLeftRadiusX)
{
_bottomLeftRadiusX = value;
invalidateSize();
invalidateDisplayList();
invalidateParentSizeAndDisplayList();
}
}
//----------------------------------
// bottomLeftRadiusY
//----------------------------------
private var _bottomLeftRadiusY:Number;
[Inspectable(category="General", minValue="0.0")]
/**
* The y radius of the bottom left corner of the rectangle.
*
* @default NaN
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get bottomLeftRadiusY():Number
{
return _bottomLeftRadiusY;
}
public function set bottomLeftRadiusY(value:Number):void
{
if (value != _bottomLeftRadiusY)
{
_bottomLeftRadiusY = value;
invalidateSize();
invalidateDisplayList();
invalidateParentSizeAndDisplayList();
}
}
//----------------------------------
// bottomRightRadiusX
//----------------------------------
private var _bottomRightRadiusX:Number;
[Inspectable(category="General", minValue="0.0")]
/**
* The x radius of the bottom right corner of the rectangle.
*
* @default NaN
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get bottomRightRadiusX():Number
{
return _bottomRightRadiusX;
}
public function set bottomRightRadiusX(value:Number):void
{
if (value != bottomRightRadiusX)
{
_bottomRightRadiusX = value;
invalidateSize();
invalidateDisplayList();
invalidateParentSizeAndDisplayList();
}
}
//----------------------------------
// bottomRightRadiusY
//----------------------------------
private var _bottomRightRadiusY:Number;
[Inspectable(category="General", minValue="0.0")]
/**
* The y radius of the bottom right corner of the rectangle.
*
* @default NaN
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get bottomRightRadiusY():Number
{
return _bottomRightRadiusY;
}
public function set bottomRightRadiusY(value:Number):void
{
if (value != _bottomRightRadiusY)
{
_bottomRightRadiusY = value;
invalidateSize();
invalidateDisplayList();
invalidateParentSizeAndDisplayList();
}
}
//----------------------------------
// radiusX
//----------------------------------
private var _radiusX:Number = 0;
[Inspectable(category="General", minValue="0.0")]
/**
* The default corner radius to use for the x axis on all corners. The
* <code>topLeftRadiusX</code>, <code>topRightRadiusX</code>,
* <code>bottomLeftRadiusX</code>, and <code>bottomRightRadiusX</code>
* properties take precedence over this property.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get radiusX():Number
{
return _radiusX;
}
public function set radiusX(value:Number):void
{
if (value != _radiusX)
{
_radiusX = value;
invalidateSize();
invalidateDisplayList();
invalidateParentSizeAndDisplayList();
}
}
//----------------------------------
// radiusY
//----------------------------------
private var _radiusY:Number = 0;
[Inspectable(category="General", minValue="0.0")]
/**
* The default corner radius to use for the y axis on all corners. The
* <code>topLeftRadiusY</code>, <code>topRightRadiusY</code>,
* <code>bottomLeftRadiusY</code>, and <code>bottomRightRadiusY</code>
* properties take precedence over this property.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get radiusY():Number
{
return _radiusY;
}
public function set radiusY(value:Number):void
{
if (value != _radiusY)
{
_radiusY = value;
invalidateSize();
invalidateDisplayList();
invalidateParentSizeAndDisplayList();
}
}
//----------------------------------
// topLeftRadiusX
//----------------------------------
private var _topLeftRadiusX:Number;
[Inspectable(category="General", minValue="0.0")]
/**
* The x radius of the top left corner of the rectangle.
*
* @default NaN
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get topLeftRadiusX():Number
{
return _topLeftRadiusX;
}
public function set topLeftRadiusX(value:Number):void
{
if (value != _topLeftRadiusX)
{
_topLeftRadiusX = value;
invalidateSize();
invalidateDisplayList();
invalidateParentSizeAndDisplayList();
}
}
//----------------------------------
// topLeftRadiusY
//----------------------------------
private var _topLeftRadiusY:Number;
[Inspectable(category="General", minValue="0.0")]
/**
* The y radius of the top left corner of the rectangle.
*
* @default NaN
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get topLeftRadiusY():Number
{
return _topLeftRadiusY;
}
public function set topLeftRadiusY(value:Number):void
{
if (value != _topLeftRadiusY)
{
_topLeftRadiusY = value;
invalidateSize();
invalidateDisplayList();
invalidateParentSizeAndDisplayList();
}
}
//----------------------------------
// topRightRadiusX
//----------------------------------
private var _topRightRadiusX:Number;
[Inspectable(category="General", minValue="0.0")]
/**
* The x radius of the top right corner of the rectangle.
*
* @default NaN
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get topRightRadiusX():Number
{
return _topRightRadiusX;
}
public function set topRightRadiusX(value:Number):void
{
if (value != topRightRadiusX)
{
_topRightRadiusX = value;
invalidateSize();
invalidateDisplayList();
invalidateParentSizeAndDisplayList();
}
}
//----------------------------------
// topRightRadiusY
//----------------------------------
private var _topRightRadiusY:Number;
[Inspectable(category="General", minValue="0.0")]
/**
* The y radius of the top right corner of the rectangle.
*
* @default NaN
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get topRightRadiusY():Number
{
return _topRightRadiusY;
}
public function set topRightRadiusY(value:Number):void
{
if (value != _topRightRadiusY)
{
_topRightRadiusY = value;
invalidateSize();
invalidateDisplayList();
invalidateParentSizeAndDisplayList();
}
}
//--------------------------------------------------------------------------
//
// Overridden methods
//
//--------------------------------------------------------------------------
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
override protected function draw(g:Graphics):void
{
// If any of the explicit radiusX values are specified, we have corner-specific rounding.
if (!isNaN(topLeftRadiusX) || !isNaN(topRightRadiusX) ||
!isNaN(bottomLeftRadiusX) || !isNaN(bottomRightRadiusX))
{
// All of the fallback rules are implemented in drawRoundRectComplex2().
GraphicsUtil.drawRoundRectComplex2(g, drawX, drawY, width, height,
radiusX, radiusY,
topLeftRadiusX, topLeftRadiusY,
topRightRadiusX, topRightRadiusY,
bottomLeftRadiusX, bottomLeftRadiusY,
bottomRightRadiusX, bottomRightRadiusY);
}
else if (radiusX != 0)
{
var rX:Number = radiusX;
var rY:Number = radiusY == 0 ? radiusX : radiusY;
g.drawRoundRect(drawX, drawY, width, height, rX * 2, rY * 2);
}
else
{
g.drawRect(drawX, drawY, width, height);
}
}
/**
* @private
*/
override protected function transformWidthForLayout(width:Number,
height:Number,
postLayoutTransform:Boolean = true):Number
{
if (postLayoutTransform && hasComplexLayoutMatrix)
width = getRoundRectBoundingBox(width, height, this,
layoutFeatures.layoutMatrix).width;
// Take stroke into account
return width + getStrokeExtents(postLayoutTransform).width;
}
/**
* @private
*/
override protected function transformHeightForLayout(width:Number,
height:Number,
postLayoutTransform:Boolean = true):Number
{
if (postLayoutTransform && hasComplexLayoutMatrix)
height = getRoundRectBoundingBox(width, height, this,
layoutFeatures.layoutMatrix).height;
// Take stroke into account
return height + getStrokeExtents(postLayoutTransform).height;
}
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
override public function getBoundsXAtSize(width:Number, height:Number, postLayoutTransform:Boolean = true):Number
{
var strokeExtents:Rectangle = getStrokeExtents(postLayoutTransform);
var m:Matrix = getComplexMatrix(postLayoutTransform);
if (!m)
return strokeExtents.left + this.x;
if (!isNaN(width))
width -= strokeExtents.width;
if (!isNaN(height))
height -= strokeExtents.height;
// Calculate the width and height pre-transform:
var newSize:Point = MatrixUtil.fitBounds(width, height, m,
explicitWidth, explicitHeight,
preferredWidthPreTransform(),
preferredHeightPreTransform(),
minWidth, minHeight,
maxWidth, maxHeight);
if (!newSize)
newSize = new Point(minWidth, minHeight);
return strokeExtents.left +
getRoundRectBoundingBox(newSize.x, newSize.y, this, m).x;
}
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
override public function getBoundsYAtSize(width:Number, height:Number, postLayoutTransform:Boolean = true):Number
{
var strokeExtents:Rectangle = getStrokeExtents(postLayoutTransform);
var m:Matrix = getComplexMatrix(postLayoutTransform);
if (!m)
return strokeExtents.top + this.y;
if (!isNaN(width))
width -= strokeExtents.width;
if (!isNaN(height))
height -= strokeExtents.height;
// Calculate the width and height pre-transform:
var newSize:Point = MatrixUtil.fitBounds(width, height, m,
explicitWidth, explicitHeight,
preferredWidthPreTransform(),
preferredHeightPreTransform(),
minWidth, minHeight,
maxWidth, maxHeight);
if (!newSize)
newSize = new Point(minWidth, minHeight);
return strokeExtents.top +
getRoundRectBoundingBox(newSize.x, newSize.y, this, m).y;
}
/**
* @private
*/
override public function getLayoutBoundsX(postLayoutTransform:Boolean = true):Number
{
var stroke:Number = getStrokeExtents(postLayoutTransform).left;
if (postLayoutTransform && hasComplexLayoutMatrix)
return stroke + getRoundRectBoundingBox(width, height, this,
layoutFeatures.layoutMatrix).x;
return stroke + this.x;
}
/**
* @private
*/
override public function getLayoutBoundsY(postLayoutTransform:Boolean = true):Number
{
var stroke:Number = getStrokeExtents(postLayoutTransform).top;
if (postLayoutTransform && hasComplexLayoutMatrix)
return stroke + getRoundRectBoundingBox(width, height, this,
layoutFeatures.layoutMatrix).y;
return stroke + this.y;
}
/**
* @private
*/
override public function setLayoutBoundsSize(width:Number,
height:Number,
postLayoutTransform:Boolean = true):void
{
super.setLayoutBoundsSize(width, height, postLayoutTransform);
var isRounded:Boolean = !isNaN(topLeftRadiusX) ||
!isNaN(topRightRadiusX) ||
!isNaN(bottomLeftRadiusX) ||
!isNaN(bottomRightRadiusX) ||
radiusX != 0 ||
radiusY != 0;
if (!isRounded)
return;
var m:Matrix = getComplexMatrix(postLayoutTransform);
if (!m)
return;
setLayoutBoundsTransformed(width, height, m);
}
/**
* @private
*/
private function setLayoutBoundsTransformed(width:Number, height:Number, m:Matrix):void
{
var strokeExtents:Rectangle = getStrokeExtents(true);
width -= strokeExtents.width;
height -= strokeExtents.height;
var size:Point = fitLayoutBoundsIterative(width, height, m);
// We couldn't find a solution, try to relax the constraints
if (!size && !isNaN(width) && !isNaN(height))
{
// Try without width constraint
var size1:Point = fitLayoutBoundsIterative(NaN, height, m);
// Try without height constraint
var size2:Point = fitLayoutBoundsIterative(width, NaN, m);
// Ignore solutions that will exceeed the requested size
if (size1 && getRoundRectBoundingBox(size1.x, size1.y, this, m).width > width)
size1 = null;
if (size2 && getRoundRectBoundingBox(size2.x, size2.y, this, m).height > height)
size2 = null;
// Which size was better?
if (size1 && size2)
{
var pickSize1:Boolean = size1.x * size1.y > size2.x * size2.y;
if (pickSize1)
size = size1;
else
size = size2;
}
else if (size1)
{
size = size1;
}
else
{
size = size2;
}
}
if (size)
setActualSize(size.x, size.y);
else
setActualSize(minWidth, minHeight);
}
/**
* Iteratively approach a solution. Returns 0 if no exact solution exists.
* NaN values for width/height mean "not constrained" in that dimesion.
*
* @private
*/
private function fitLayoutBoundsIterative(width:Number, height:Number, m:Matrix):Point
{
var newWidth:Number = this.preferredWidthPreTransform();
var newHeight:Number = this.preferredHeightPreTransform();
var fitWidth:Number = MatrixUtil.transformBounds(newWidth, newHeight, m).x;
var fitHeight:Number = MatrixUtil.transformBounds(newWidth, newHeight, m).y;
if (isNaN(width))
fitWidth = NaN;
if (isNaN(height))
fitHeight = NaN;
var i:int = 0;
while (i++ < 150)
{
var roundedRectBounds:Rectangle = getRoundRectBoundingBox(newWidth, newHeight, this, m);
var widthDifference:Number = isNaN(width) ? 0 : width - roundedRectBounds.width;
var heightDifference:Number = isNaN(height) ? 0 : height - roundedRectBounds.height;
if (Math.abs(widthDifference) < 0.1 && Math.abs(heightDifference) < 0.1)
{
return new Point(newWidth, newHeight);
}
fitWidth += widthDifference * 0.5;
fitHeight += heightDifference * 0.5;
var newSize:Point = MatrixUtil.fitBounds(fitWidth,
fitHeight,
m,
explicitWidth,
explicitHeight,
preferredWidthPreTransform(),
preferredHeightPreTransform(),
minWidth, minHeight,
maxWidth, maxHeight);
if (!newSize)
break;
newWidth = newSize.x;
newHeight = newSize.y;
}
return null;
}
/**
* @private
*/
static private function getRoundRectBoundingBox(width:Number,
height:Number,
r:Rect,
m:Matrix):Rectangle
{
// We can find the round rect bounds by finding the
// bounds of the four ellipses at the four corners
// Make sure that radiusX & radiusY don't exceed the width & height:
var maxRadiusX:Number = width / 2;
var maxRadiusY:Number = height / 2;
var radiusX:Number = r.radiusX;
var radiusY:Number = r.radiusY == 0 ? radiusX : r.radiusY;
function radiusValue(def:Number, value:Number, max:Number):Number
{
var result:Number = isNaN(value) ? def : value;
return Math.min(result, max);
}
var boundingBox:Rectangle;
var rX:Number;
var rY:Number;
// top-left corner ellipse
rX = radiusValue(radiusX, r.topLeftRadiusX, maxRadiusX);
rY = radiusValue(radiusY, r.topLeftRadiusY, maxRadiusY);
boundingBox = MatrixUtil.getEllipseBoundingBox(rX, rY, rX, rY, m, boundingBox);
// top-right corner ellipse
rX = radiusValue(radiusX, r.topRightRadiusX, maxRadiusX);
rY = radiusValue(radiusY, r.topRightRadiusY, maxRadiusY);
boundingBox = MatrixUtil.getEllipseBoundingBox(width - rX, rY, rX, rY, m, boundingBox);
// bottom-right corner ellipse
rX = radiusValue(radiusX, r.bottomRightRadiusX, maxRadiusX);
rY = radiusValue(radiusY, r.bottomRightRadiusY, maxRadiusY);
boundingBox = MatrixUtil.getEllipseBoundingBox(width - rX, height - rY, rX, rY, m, boundingBox);
// bottom-left corner ellipse
rX = radiusValue(radiusX, r.bottomLeftRadiusX, maxRadiusX);
rY = radiusValue(radiusY, r.bottomLeftRadiusY, maxRadiusY);
boundingBox = MatrixUtil.getEllipseBoundingBox(rX, height - rY, rX, rY, m, boundingBox);
return boundingBox;
}
}
}