////////////////////////////////////////////////////////////////////////////////
//
// 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.mobile.supportClasses
{
import flash.display.DisplayObject;
import flash.display.GradientType;
import flash.display.Graphics;
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 mx.utils.ColorUtil;
import spark.components.supportClasses.SkinnableComponent;
import spark.components.supportClasses.StyleableTextField;
import spark.core.DisplayObjectSharingMode;
import spark.core.IGraphicElement;
import spark.skins.IHighlightBitmapCaptureClient;
use namespace mx_internal;
/**
* ActionScript-based skin for mobile applications. This skin is the
* base class for all of the ActionScript mobile skins. As an optimization,
* it removes state transition support.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public class MobileSkin extends UIComponent implements IHighlightBitmapCaptureClient
{
//--------------------------------------------------------------------------
//
// Class constants
//
//--------------------------------------------------------------------------
/**
* @private
* Dark chromeColor
used in ViewNavigator and
* TabbedViewNavigator "chrome" elements: ActionBar and
* TabbedViewNavigator#tabBar.
*/
mx_internal static const MOBILE_THEME_DARK_COLOR:uint = 0x484848;
/**
* @private
* Default chromeColor
for the mobile theme.
*/
mx_internal static const MOBILE_THEME_LIGHT_COLOR:uint = 0xCCCCCC;
/**
* @private
* Default symbol color for symbolColor
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 MobileSkin()
{
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* Specifies whether or not this skin should be affected by the symbolColor
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 = true;
/**
* Specifies a default width. measuredWidth
returns this value
* when the computed measuredWidth
is less than
* measuredDefaultWidth
.
*
* @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. measuredHeight
returns this value
* when the computed measuredHeight
is less than
* measuredDefaultHeight
.
*
* @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 color
property defined by
* the symbolColor
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.
*
*
This method, along with colorizeContents()
, is called
* by the updateDisplayList()
method.
This method positions skin parts and graphic children of the skin. * Subclasses should override this to position their children.
* * @param unscaledWidth Specifies the width of the component, in pixels, * in the component's coordinates, regardless of the value of the *scaleX
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
* scaleY
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.
*
* This method, along with layoutContents()
, is called
* by the updateDisplayList()
.
This method draws the background chromeColor. * Override this method to change the appearance of the chromeColor.
* * @param unscaledWidth Specifies the width of the component, in pixels, * in the component's coordinates, regardless of the value of the *scaleX
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
* scaleY
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.
*
* 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.
* *Call this method after calling setElementSize()
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.
* *Call this method before calling the setElementPosition()
method.
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.
* * @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. * *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.
* * @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. * * @returntrue
if the component needs to be redrawn; otherwise false
.
*
* @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 true
if the component needs to be redrawn; otherwise false
.
*
* @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 (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;
}
}
}