blob: 5d0242f4ce9d84947117b33dd72a739ec2b1c41c [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.components.supportClasses
{
import mx.core.ILayoutElement;
import spark.components.IconPlacement;
import spark.components.supportClasses.GroupBase;
import spark.core.IDisplayText;
import spark.layouts.supportClasses.LayoutBase;
import spark.layouts.supportClasses.LayoutElementHelper;
[ExcludeClass]
/**
* Helper layout to layout a label relative to a sibling element.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4.5
*/
public class LabelAndIconLayout extends LayoutBase
{
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function LabelAndIconLayout():void
{
super();
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* @private
*/
private var labelElement:ILayoutElement;
/**
* @private
*/
private var iconElement:ILayoutElement;
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// gap
//----------------------------------
private var _gap:int = 6;
[Inspectable(category="General")]
/**
* The horizontal space between layout elements, in pixels.
*
* Note that the gap is only applied between layout elements, so if there's
* just one element, the gap has no effect on the layout.
*
* @default 6
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get gap():int
{
return _gap;
}
/**
* @private
*/
public function set gap(value:int):void
{
if (_gap == value)
return;
_gap = value;
invalidateTargetSizeAndDisplayList();
}
//----------------------------------
// iconPlacement
//----------------------------------
private var _iconPlacement:String = IconPlacement.LEFT;
[Inspectable(category="General")]
/**
* Position of icon relative to label.
*
* @default IconPlacement.LEFT
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get iconPlacement():String
{
return _iconPlacement;
}
/**
* @private
*/
public function set iconPlacement(value:String):void
{
if (_iconPlacement == value)
return;
_iconPlacement = value;
invalidateTargetSizeAndDisplayList();
}
//----------------------------------
// paddingLeft
//----------------------------------
private var _paddingLeft:Number = 0;
[Inspectable(category="General")]
/**
* Number of pixels between the container's left edge
* and the left edge of the first layout element.
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get paddingLeft():Number
{
return _paddingLeft;
}
/**
* @private
*/
public function set paddingLeft(value:Number):void
{
if (_paddingLeft == value)
return;
_paddingLeft = value;
invalidateTargetSizeAndDisplayList();
}
//----------------------------------
// paddingRight
//----------------------------------
private var _paddingRight:Number = 0;
[Inspectable(category="General")]
/**
* Number of pixels between the container's right edge
* and the right edge of the last layout element.
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get paddingRight():Number
{
return _paddingRight;
}
/**
* @private
*/
public function set paddingRight(value:Number):void
{
if (_paddingRight == value)
return;
_paddingRight = value;
invalidateTargetSizeAndDisplayList();
}
//----------------------------------
// paddingTop
//----------------------------------
private var _paddingTop:Number = 0;
[Inspectable(category="General")]
/**
* The minimum number of pixels between the container's top edge and
* the top of all the container's layout elements.
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get paddingTop():Number
{
return _paddingTop;
}
/**
* @private
*/
public function set paddingTop(value:Number):void
{
if (_paddingTop == value)
return;
_paddingTop = value;
invalidateTargetSizeAndDisplayList();
}
//----------------------------------
// paddingBottom
//----------------------------------
private var _paddingBottom:Number = 0;
[Inspectable(category="General")]
/**
* The minimum number of pixels between the container's bottom edge and
* the bottom of all the container's layout elements.
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get paddingBottom():Number
{
return _paddingBottom;
}
/**
* @private
*/
public function set paddingBottom(value:Number):void
{
if (_paddingBottom == value)
return;
_paddingBottom = value;
invalidateTargetSizeAndDisplayList();
}
//--------------------------------------------------------------------------
//
// LayoutBase methods
//
//--------------------------------------------------------------------------
/**
* @private
*/
override public function measure():void
{
super.measure();
var layoutTarget:GroupBase = target;
if (!layoutTarget)
return;
var width:Number = _paddingLeft + _paddingRight;
var height:Number = _paddingTop + _paddingBottom;
var horizontal:Boolean =
iconPlacement == IconPlacement.LEFT ||
iconPlacement == IconPlacement.RIGHT;
assignLayoutElements();
var iconHeight:Number = iconElement ? iconElement.getPreferredBoundsHeight() : 0;
var iconWidth:Number = iconElement ? iconElement.getPreferredBoundsWidth() : 0;
var labelWidth:Number = labelElement && IDisplayText(labelElement).text ?
labelElement.getPreferredBoundsWidth() : 0;
var labelHeight:Number = labelElement && IDisplayText(labelElement).text ?
labelElement.getPreferredBoundsHeight() : 0;
if (horizontal)
{
width += labelWidth + iconWidth;
if (labelWidth && iconWidth)
width += _gap;
height += Math.max(labelHeight, iconHeight);
}
else
{
width += Math.max(labelWidth, iconWidth);
height += labelHeight + iconHeight;
if (labelHeight && iconHeight)
height += _gap;
}
layoutTarget.measuredWidth = Math.ceil(width);
layoutTarget.measuredHeight = Math.ceil(height);
}
/**
* @private
*/
override public function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
var layoutTarget:GroupBase = target;
if (!layoutTarget)
return;
var width:Number = _paddingLeft + _paddingRight;
var height:Number = _paddingTop + _paddingBottom;
var gap:Number;
var horizontal:Boolean =
iconPlacement == IconPlacement.LEFT ||
iconPlacement == IconPlacement.RIGHT;
assignLayoutElements();
var iconHeight:Number = iconElement ? iconElement.getPreferredBoundsHeight() : 0;
var iconWidth:Number = iconElement ? iconElement.getPreferredBoundsWidth() : 0;
var validLabel:Boolean = labelElement && IDisplayText(labelElement).text;
var labelWidth:Number = validLabel ? labelElement.getPreferredBoundsWidth() : 0;
var labelHeight:Number = validLabel ? labelElement.getPreferredBoundsHeight() : 0;
var labelVerticalCenter:Number = validLabel ?
LayoutElementHelper.parseConstraintValue(labelElement.verticalCenter) : 0;
var labelX:Number = 0;
var labelY:Number = 0;
var iconX:Number = 0;
var iconY:Number = 0;
var viewWidth:Number = unscaledWidth - _paddingLeft - _paddingRight;
var viewHeight:Number = unscaledHeight - _paddingTop - _paddingBottom;
if (horizontal)
{
gap = (iconWidth && labelWidth) ? _gap : 0;
if (labelWidth > 0)
{
labelWidth = Math.max(Math.min(viewWidth - iconWidth - gap, labelWidth), 0);
}
labelHeight = Math.min(unscaledHeight, labelHeight);
labelX = ((viewWidth - labelWidth - iconWidth - gap) / 2) + paddingLeft;
if (iconPlacement == IconPlacement.LEFT)
{
labelX += iconWidth + gap;
iconX = labelX - (iconWidth + gap);
}
else
{
iconX = labelX + labelWidth + gap;
}
// Collapse padding as necessary if we're lacking the space.
if ((_paddingLeft + _paddingRight + iconWidth) > unscaledWidth)
iconX = (unscaledWidth/2 - iconWidth/2);
iconY = ((viewHeight - iconHeight)/2) + _paddingTop;
labelY = ((viewHeight - labelHeight)/2) +
_paddingTop + labelVerticalCenter;
}
else
{
gap = (iconHeight && labelHeight) ? _gap : 0;
if (labelWidth > 0)
{
labelWidth = Math.max(viewWidth, 0);
labelHeight = Math.min(viewHeight - iconHeight - gap, labelHeight);
}
labelX = _paddingLeft;
iconX += ((viewWidth - iconWidth) / 2) + _paddingLeft;
if (iconPlacement == IconPlacement.BOTTOM)
{
labelY += ((viewHeight - labelHeight - iconHeight - gap)/2) +
labelVerticalCenter + _paddingTop;
iconY += labelY + labelHeight + gap;
}
else
{
iconY = ((viewHeight - labelHeight- iconHeight - gap)/2) + _paddingTop;
labelY += iconY + iconHeight + gap + labelVerticalCenter;
}
// Collapse padding as necessary if we're lacking the space.
if ((_paddingTop + _paddingBottom + iconHeight) > unscaledHeight)
iconY = (unscaledHeight/2 - iconHeight/2);
}
if (labelElement)
{
labelElement.setLayoutBoundsSize(labelWidth, labelHeight);
labelElement.setLayoutBoundsPosition(Math.ceil(labelX), Math.ceil(labelY));
}
if (iconElement)
{
iconElement.setLayoutBoundsSize(iconWidth, iconHeight);
iconElement.setLayoutBoundsPosition(Math.ceil(iconX), Math.ceil(iconY));
}
// Make sure that if the content spans partially over a pixel to the right/bottom,
// the content size includes the whole pixel.
layoutTarget.setContentSize(
Math.ceil(Math.max(unscaledWidth, Math.max(iconWidth, labelWidth))),
Math.ceil(Math.max(unscaledHeight, Math.max(iconHeight, labelHeight)))
);
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* @private
* Convenience function for subclasses that invalidates the
* target's size and displayList so that both layout's <code>measure()</code>
* and <code>updateDisplayList</code> methods get called.
*
* <p>Typically a layout invalidates the target's size and display list so that
* it gets a chance to recalculate the target's default size and also size and
* position the target's elements. For example changing the <code>gap</code>
* property on a <code>VerticalLayout</code> will internally call this method
* to ensure that the elements are re-arranged with the new setting and the
* target's default size is recomputed.</p>
*/
private function invalidateTargetSizeAndDisplayList():void
{
var g:GroupBase = target;
if (!g)
return;
g.invalidateSize();
g.invalidateDisplayList();
}
/**
* @private
* Determine which is our label and which is its sibling.
* Technically we're expecting at most two elements here
* but its not fatal if there are more, though one may obtain
* unexpected results.
*/
private function assignLayoutElements():void
{
for (var i:int = 0; i < target.numElements; i++)
{
var layoutElement:ILayoutElement = target.getElementAt(i);
if (!layoutElement || !layoutElement.includeInLayout)
continue;
if (layoutElement is IDisplayText)
labelElement = layoutElement;
else
iconElement = layoutElement;
}
}
}
}