blob: 495312da5c6cfd4964a4a91f043f539314bbf777 [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.skins
{
import flash.display.DisplayObject;
import flash.geom.ColorTransform;
import flash.geom.Matrix;
import mx.core.FlexGlobals;
import mx.core.IFlexDisplayObject;
import mx.core.ILayoutElement;
import mx.core.UIComponent;
import mx.core.mx_internal;
import spark.components.supportClasses.SkinnableComponent;
import spark.core.DisplayObjectSharingMode;
import spark.core.IGraphicElement;
use namespace mx_internal;
/**
* ActionScript-based skin for both mobile and desktop applications.
* This skin is the base class for MobileSkin.
* As an optimization, it removes state transition support.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public class ActionScriptSkinBase extends UIComponent implements IHighlightBitmapCaptureClient
{
//--------------------------------------------------------------------------
//
// Class constants
//
//--------------------------------------------------------------------------
/**
* @private
* Default symbol color for <code>symbolColor</code> style.
*/
mx_internal static const DEFAULT_SYMBOL_COLOR_VALUE:uint = 0x00;
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*
*/
public function ActionScriptSkinBase()
{
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* Specifies whether or not this skin should be affected by the <code>symbolColor</code> style.
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
protected var useSymbolColor:Boolean = false;
/**
* @private
* Toggles transparent, centered hit-area if the unscaled size is less
* than one-quarter inch square. Physical size is based on applicationDPI.
*/
mx_internal var useMinimumHitArea:Boolean = false;
/**
* Specifies a default width. <code>measuredWidth</code> returns this value
* when the computed <code>measuredWidth</code> is less than
* <code>measuredDefaultWidth</code>.
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
protected var measuredDefaultWidth:Number = 0;
/**
* Specifies a default height. <code>measuredHeight</code> returns this value
* when the computed <code>measuredHeight</code> is less than
* <code>measuredDefaultHeight</code>.
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
protected var measuredDefaultHeight:Number = 0;
//----------------------------------
// colorMatrix
//----------------------------------
private static var _colorMatrix:Matrix = new Matrix();
/**
* @private
*/
mx_internal static function get colorMatrix():Matrix
{
if (!_colorMatrix)
_colorMatrix = new Matrix();
return _colorMatrix;
}
//----------------------------------
// colorTransform
//----------------------------------
private static var _colorTransform:ColorTransform;
/**
* @private
*/
mx_internal static function get colorTransform():ColorTransform
{
if (!_colorTransform)
_colorTransform = new ColorTransform();
return _colorTransform;
}
//----------------------------------
// applicationDPI
//----------------------------------
/**
* Returns the DPI of the application. This property can only be set in MXML on the root application.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
protected function get applicationDPI():Number
{
return FlexGlobals.topLevelApplication.applicationDPI;
}
//----------------------------------
// symbolItems
//----------------------------------
/**
* Names of items that should have their <code>color</code> property defined by
* the <code>symbolColor</code> style.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
protected function get symbolItems():Array
{
return null;
}
//----------------------------------
// currentState
//----------------------------------
private var _currentState:String;
/**
* @private
*/
override public function get currentState():String
{
return _currentState;
}
/**
* @private
*/
override public function set currentState(value:String):void
{
if (value != _currentState)
{
_currentState = value;
commitCurrentState();
}
}
/**
* Called whenever the currentState changes. Skins should override
* this function if they make any appearance changes during
* a state change.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
protected function commitCurrentState():void
{
}
/**
* MobileSkin does not use states. Skins should override this function
* to return false for states that are not implemented.
*
* @param stateName The state name.
*
* @return false for states that are not implemented.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
override public function hasState(stateName:String):Boolean
{
return true;
}
/**
* @private
*/
override public function setCurrentState(stateName:String,
playTransition:Boolean = true):void
{
currentState = stateName;
}
/**
* @private
*/
override public function get measuredWidth():Number
{
return Math.max(super.measuredWidth, measuredDefaultWidth);
}
/**
* @private
*/
override public function get measuredHeight():Number
{
return Math.max(super.measuredHeight, measuredDefaultHeight);
}
/**
* @private
*/
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
graphics.clear();
super.updateDisplayList(unscaledWidth, unscaledHeight);
layoutContents(unscaledWidth, unscaledHeight);
if (useSymbolColor)
applySymbolColor();
if (useMinimumHitArea)
drawMinimumHitArea(unscaledWidth, unscaledHeight);
drawBackground(unscaledWidth, unscaledHeight);
}
/**
* @private
* Make the component's explicitMinWidth property override its skin's.
* This is useful for cases where the skin's minWidth constrains
* the skin's measured size. In those cases the user could set
* explicit limits on the component itself thus relaxing the
* hard-coded limits in the skin. See SDK-24741.
*/
override public function get explicitMinWidth():Number
{
if (parent is SkinnableComponent)
{
var parentExplicitMinWidth:Number = SkinnableComponent(parent).explicitMinWidth;
if (!isNaN(parentExplicitMinWidth))
return parentExplicitMinWidth;
}
return super.explicitMinWidth;
}
/**
* @private
* Make the component's explicitMinWidth property override its skin's.
* This is useful for cases where the skin's minWidth constrains
* the skin's measured size. In those cases the user could set
* explicit limits on the component itself thus relaxing the
* hard-coded limits in the skin. See SDK-24741.
*/
override public function get explicitMinHeight():Number
{
if (parent is SkinnableComponent)
{
var parentExplicitMinHeight:Number = SkinnableComponent(parent).explicitMinHeight;
if (!isNaN(parentExplicitMinHeight))
return parentExplicitMinHeight;
}
return super.explicitMinHeight;
}
/**
* @private
* Make the component's explicitMinWidth property override its skin's.
* This is useful for cases where the skin's minWidth constrains
* the skin's measured size. In those cases the user could set
* explicit limits on the component itself thus relaxing the
* hard-coded limits in the skin. See SDK-24741.
*/
override public function get explicitMaxWidth():Number
{
if (parent is SkinnableComponent)
{
var parentExplicitMaxWidth:Number = SkinnableComponent(parent).explicitMaxWidth;
if (!isNaN(parentExplicitMaxWidth))
return parentExplicitMaxWidth;
}
return super.explicitMaxWidth;
}
/**
* @private
* Make the component's explicitMinWidth property override its skin's.
* This is useful for cases where the skin's minWidth constrains
* the skin's measured size. In those cases the user could set
* explicit limits on the component itself thus relaxing the
* hard-coded limits in the skin. See SDK-24741.
*/
override public function get explicitMaxHeight():Number
{
if (parent is SkinnableComponent)
{
var parentExplicitMaxHeight:Number = SkinnableComponent(parent).explicitMaxHeight;
if (!isNaN(parentExplicitMaxHeight))
return parentExplicitMaxHeight;
}
return super.explicitMaxHeight;
}
//--------------------------------------------------------------------------
//
// Class methods
//
//--------------------------------------------------------------------------
mx_internal function drawMinimumHitArea(unscaledWidth:Number, unscaledHeight:Number):void
{
// minimum hit area is 0.25 inches square
var minSize:Number = applicationDPI / 4;
// skip if skin size is larger than minimum
if ((unscaledWidth > minSize) && (unscaledHeight > minSize))
return;
// center a transparent hit area larger than the skin
var hitAreaWidth:Number = Math.max(minSize, unscaledWidth);
var hitAreaHeight:Number = Math.max(minSize, unscaledHeight);
var hitAreaX:Number = (unscaledWidth - hitAreaWidth) / 2;
var hitAreaY:Number = (unscaledHeight - hitAreaHeight) / 2;
graphics.beginFill(0, 0);
graphics.drawRect(hitAreaX, hitAreaY, hitAreaWidth, hitAreaHeight);
graphics.endFill();
}
/**
* Positions the children for this skin.
*
* <p>This method, along with <code>colorizeContents()</code>, is called
* by the <code>updateDisplayList()</code> method.</p>
*
* <p>This method positions skin parts and graphic children of the skin.
* Subclasses should override this to position their children.</p>
*
* @param unscaledWidth Specifies the width of the component, in pixels,
* in the component's coordinates, regardless of the value of the
* <code>scaleX</code> property of the component.
*
* @param unscaledHeight Specifies the height of the component, in pixels,
* in the component's coordinates, regardless of the value of the
* <code>scaleY</code> property of the component.
*
* @langversion 3.0
* @playerversion Flash 10.1
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
protected function layoutContents(unscaledWidth:Number, unscaledHeight:Number):void
{
}
/**
* A helper method to set a color transform on a DisplayObject.
*
* @param displayObject The display object to transform
* @param originalColor The original color
* @param tintColor The desired color
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
protected function applyColorTransform(displayObject:DisplayObject, originalColor:uint, tintColor:uint):void
{
colorTransform.redOffset = ((tintColor & (0xFF << 16)) >> 16) - ((originalColor & (0xFF << 16)) >> 16);
colorTransform.greenOffset = ((tintColor & (0xFF << 8)) >> 8) - ((originalColor & (0xFF << 8)) >> 8);
colorTransform.blueOffset = (tintColor & 0xFF) - (originalColor & 0xFF);
colorTransform.alphaMultiplier = alpha;
displayObject.transform.colorTransform = colorTransform;
}
/**
* Renders a background for the skin.
*
* <p>This method, along with <code>layoutContents()</code>, is called
* by the <code>updateDisplayList()</code>.</p>
*
* <p>This method draws the background chromeColor.
* Override this method to change the appearance of the chromeColor.</p>
*
* @param unscaledWidth Specifies the width of the component, in pixels,
* in the component's coordinates, regardless of the value of the
* <code>scaleX</code> property of the component.
*
* @param unscaledHeight Specifies the height of the component, in pixels,
* in the component's coordinates, regardless of the value of the
* <code>scaleY</code> property of the component.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
protected function drawBackground(unscaledWidth:Number, unscaledHeight:Number):void
{
}
/**
* @private
*/
mx_internal function applySymbolColor():void
{
var symbols:Array = symbolItems;
var len:uint = (symbols) ? symbols.length : 0;
if (len > 0)
{
var symbolColor:uint = getStyle("symbolColor");
var symbolObj:Object;
var transformInitialized:Boolean = false;
for (var i:uint = 0; i < len; i++)
{
symbolObj = this[symbols[i]];
// SparkSkin assumed symbols were IFill objects
// with a color property. MobileSkin instead assumes symbols
// are DisplayObjects.
if (symbolObj is DisplayObject)
{
if (!transformInitialized)
{
colorTransform.redOffset = ((symbolColor & (0xFF << 16)) >> 16) - DEFAULT_SYMBOL_COLOR_VALUE;
colorTransform.greenOffset = ((symbolColor & (0xFF << 8)) >> 8) - DEFAULT_SYMBOL_COLOR_VALUE;
colorTransform.blueOffset = (symbolColor & 0xFF) - DEFAULT_SYMBOL_COLOR_VALUE;
colorTransform.alphaMultiplier = alpha;
transformInitialized = true;
}
DisplayObject(symbolObj).transform.colorTransform = colorTransform;
}
}
}
}
/**
* A helper method to position children elements of this component.
*
* <p>This method is the recommended way to position children elements. You can
* use this method instead of checking for and using
* various interfaces/classes such as ILayoutElement, IFlexDisplayObject,
* or StyleableTextField.</p>
*
* <p>Call this method after calling <code>setElementSize()</code></p>
*
* @param element The child element to position. The element could be an
* ILayoutElement, IFlexDisplayObject, StyleableTextField, or a generic
* DisplayObject.
*
* @param x The x-coordinate of the child.
*
* @param y The y-coordinate of the child.
*
* @see #setElementSize
*
* @langversion 3.0
* @playerversion Flash 10.1
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
protected function setElementPosition(element:Object, x:Number, y:Number):void
{
if (element is ILayoutElement)
{
ILayoutElement(element).setLayoutBoundsPosition(x, y, false);
}
else if (element is IFlexDisplayObject)
{
IFlexDisplayObject(element).move(x, y);
}
else
{
element.x = x;
element.y = y;
}
}
/**
* A helper method to size children elements of this component.
*
* <p>This method is the recommended way to size children elements. You can
* use this method instead of checking for and using
* interfaces/classes such as ILayoutElement, IFlexDisplayObject,
* or StyleableTextField.</p>
*
* <p>Call this method before calling the <code>setElementPosition()</code> method.</p>
*
* @param element The child element to size. The element could be an
* ILayoutElement, IFlexDisplayObject, StyleableTextField, or a generic
* DisplayObject.
*
* @param width The width of the child.
*
* @param height The height of the child.
*
* @see #setElementPosition
*
* @langversion 3.0
* @playerversion Flash 10.1
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
protected function setElementSize(element:Object, width:Number, height:Number):void
{
if (element is ILayoutElement)
{
ILayoutElement(element).setLayoutBoundsSize(width, height, false);
}
else if (element is IFlexDisplayObject)
{
IFlexDisplayObject(element).setActualSize(width, height);
}
else
{
element.width = width;
element.height = height;
}
}
/**
* A helper method to retrieve the preferred width of a child element.
*
* <p>This method is the recommended way to get a child element's preferred
* width. You can use this method instead of checking for and using
* various interfaces/classes such as ILayoutElement, IFlexDisplayObject,
* or StyleableTextField.</p>
*
* @param element The child element to retrieve the width for. The element could
* be an ILayoutElement, IFlexDisplayObject, StyleableTextField, or a generic
* DisplayObject.
*
* @return The child element's preferred width.
*
* @see #sizeElement
* @see #getElementPreferredHeight
*
* @langversion 3.0
* @playerversion Flash 10.1
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
protected function getElementPreferredWidth(element:Object):Number
{
if (element is ILayoutElement)
{
return ILayoutElement(element).getPreferredBoundsWidth();
}
else if (element is IFlexDisplayObject)
{
return IFlexDisplayObject(element).measuredWidth;
}
else
{
return element.width;
}
}
/**
* A helper method to retrieve the preferred height of a child element.
*
* <p>This method is the recommended way to get a child element's preferred
* height. You can use this method instead of checking for and using
* various interfaces/classes such as ILayoutElement, IFlexDisplayObject,
* or StyleableTextField.</p>
*
* @param element The child element to retrieve the height for. The element could
* be an ILayoutElement, IFlexDisplayObject, StyleableTextField, or a generic
* DisplayObject.
*
* @return The child element's preferred height.
*
* @see #sizeElement
* @see #getElementPreferredWidth
*
* @langversion 3.0
* @playerversion Flash 10.1
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
protected function getElementPreferredHeight(element:Object):Number
{
if (element is ILayoutElement)
{
return ILayoutElement(element).getPreferredBoundsHeight();
}
else if (element is IFlexDisplayObject)
{
return IFlexDisplayObject(element).measuredHeight;
}
else
{
return element.height;
}
}
/**
* List of IDs of items that should be excluded when rendering the focus ring.
* Only items of type DisplayObject or GraphicElement should be excluded. Items
* of other types are ignored.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
protected function get focusSkinExclusions():Array
{
return null;
}
private static var exclusionAlphaValues:Array;
private static var oldContentBackgroundAlpha:Number;
private static var contentBackgroundAlphaSetLocally:Boolean;
/**
* Called before a bitmap capture is made for this skin. The default implementation
* excludes items in the focusSkinExclusions array.
*
* @return <code>true</code> if the component needs to be redrawn; otherwise <code>false</code>.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function beginHighlightBitmapCapture():Boolean
{
var exclusions:Array = focusSkinExclusions;
if (!exclusions)
{
if (("hostComponent" in this) && this["hostComponent"] is SkinnableComponent)
exclusions = SkinnableComponent(this["hostComponent"]).suggestedFocusSkinExclusions;
}
var exclusionCount:Number = (exclusions == null) ? 0 : exclusions.length;
/* we'll store off the previous alpha of the exclusions so we
can restore them when we're done
*/
exclusionAlphaValues = [];
var needRedraw:Boolean = false;
for (var i:int = 0; i < exclusionCount; i++)
{
// skip if the part isn't there
if (!(exclusions[i] in this))
continue;
var ex:Object = this[exclusions[i]];
/* we're going to go under the covers here to try and modify alpha with the least
amount of disruption to the component. For UIComponents, we go to Sprite's alpha property;
*/
if (ex is UIComponent)
{
exclusionAlphaValues[i] = (ex as UIComponent).$alpha;
(ex as UIComponent).$alpha = 0;
}
else if (ex is DisplayObject)
{
exclusionAlphaValues[i] = (ex as DisplayObject).alpha;
(ex as DisplayObject).alpha = 0;
}
else if (ex is IGraphicElement)
{
/* if we're lucky, the IGE has its own DisplayObject, and we can just trip its alpha.
If not, we're going to have to set it to 0, and force a redraw of the whole component */
var ge:IGraphicElement = ex as IGraphicElement;
if (ge.displayObjectSharingMode == DisplayObjectSharingMode.OWNS_UNSHARED_OBJECT)
{
exclusionAlphaValues[i] = ge.displayObject.alpha;
ge.displayObject.alpha = 0;
}
else
{
exclusionAlphaValues[i] = ge.alpha;
ge.alpha = 0;
needRedraw = true;
}
}
}
// If we have a mostly-transparent content background, temporarily bump
// up the contentBackgroundAlpha so the captured bitmap includes an opaque
// snapshot of the background.
if (getStyle("contentBackgroundAlpha") < 0.5)
{
if (styleDeclaration && styleDeclaration.getStyle("contentBackgroundAlpha") !== null)
contentBackgroundAlphaSetLocally = true;
else
contentBackgroundAlphaSetLocally = false;
oldContentBackgroundAlpha = getStyle("contentBackgroundAlpha");
setStyle("contentBackgroundAlpha", 0.5);
needRedraw = true;
}
/* if we excluded an IGE without its own DO, we need to update the component before grabbing the bitmap */
return needRedraw;
}
/**
* Called after a bitmap capture is made for this skin. The default implementation
* restores the items in the focusSkinExclusions array.
*
* @return <code>true</code> if the component needs to be redrawn; otherwise <code>false</code>.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function endHighlightBitmapCapture():Boolean
{
var exclusions:Array = focusSkinExclusions;
if (!exclusions)
{
if ("hostComponent" in this && this["hostComponent"] is SkinnableComponent)
exclusions = SkinnableComponent(this["hostComponent"]).suggestedFocusSkinExclusions;
}
var exclusionCount:Number = (exclusions == null) ? 0 : exclusions.length;
var needRedraw:Boolean = false;
for (var i:int=0; i < exclusionCount; i++)
{
// skip if the part isn't there
if (!(exclusions[i] in this))
continue;
var ex:Object = this[exclusions[i]];
if (ex is UIComponent)
{
(ex as UIComponent).$alpha = exclusionAlphaValues[i];
}
else if (ex is DisplayObject)
{
(ex as DisplayObject).alpha = exclusionAlphaValues[i];
}
else if (ex is IGraphicElement)
{
var ge:IGraphicElement = ex as IGraphicElement;
if (ge.displayObjectSharingMode == DisplayObjectSharingMode.OWNS_UNSHARED_OBJECT)
{
ge.displayObject.alpha = exclusionAlphaValues[i];
}
else
{
ge.alpha = exclusionAlphaValues[i];
needRedraw = true;
}
}
}
exclusionAlphaValues = null;
if (!isNaN(oldContentBackgroundAlpha))
{
if (contentBackgroundAlphaSetLocally)
setStyle("contentBackgroundAlpha", oldContentBackgroundAlpha);
else
clearStyle("contentBackgroundAlpha");
needRedraw = true;
oldContentBackgroundAlpha = NaN;
}
return needRedraw;
}
}
}