blob: 1e46d38427314ea411adc83e4ac2cf15f6df9558 [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.ios7
{
import flash.display.GradientType;
import flash.events.Event;
import flash.text.TextFormatAlign;
import mx.core.DPIClassification;
import mx.core.mx_internal;
import mx.utils.ColorUtil;
import spark.components.ActionBar;
import spark.components.Group;
import spark.components.supportClasses.StyleableTextField;
import spark.core.SpriteVisualElement;
import spark.layouts.HorizontalAlign;
import spark.layouts.HorizontalLayout;
import spark.layouts.VerticalAlign;
import spark.skins.ios7.assets.ActionBarBackground;
import spark.skins.mobile.supportClasses.MobileSkin;
use namespace mx_internal;
/**
* The default skin class for the Spark ActionBar component in mobile
* applications.
*
* @see spark.components.ActionBar
* @see spark.skins.mobile.TransparentNavigationButtonSkin
* @see spark.skins.mobile.BeveledBackButtonSkin
* @see spark.skins.mobile.TransparentActionButtonSkin
* @see spark.skins.mobile.BeveledActionButtonSkin
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public class ActionBarSkin extends MobileSkin
{
//--------------------------------------------------------------------------
//
// Class constants
//
//--------------------------------------------------------------------------
/**
* @private
*/
mx_internal static const ACTIONBAR_CHROME_COLOR_RATIOS:Array = [0, 80];
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function ActionBarSkin()
{
super();
borderClass = spark.skins.ios7.assets.ActionBarBackground;
switch (applicationDPI)
{
case DPIClassification.DPI_640:
{
borderSize = 2;
layoutContentGroupHeight = 172;
layoutTitleGroupHorizontalPadding = 52;
break;
}
case DPIClassification.DPI_480:
{
borderSize = 2;
layoutContentGroupHeight = 130;
layoutTitleGroupHorizontalPadding = 40;
break;
}
case DPIClassification.DPI_320:
{
borderSize = 2;
layoutContentGroupHeight = 86;
layoutTitleGroupHorizontalPadding = 26;
break;
}
case DPIClassification.DPI_240:
{
borderSize = 1;
layoutContentGroupHeight = 65;
layoutTitleGroupHorizontalPadding = 20;
break;
}
case DPIClassification.DPI_120:
{
borderSize = 1;
layoutContentGroupHeight = 32;
layoutTitleGroupHorizontalPadding = 10;
break;
}
default:
{
// default DPI_160
borderSize = 1;
layoutContentGroupHeight = 43;
layoutTitleGroupHorizontalPadding = 13;
break;
}
}
}
//--------------------------------------------------------------------------
//
// Graphics variables
//
//--------------------------------------------------------------------------
/**
* FXG Class reference for the ActionBar background border graphic.
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
protected var borderClass:Class;
//--------------------------------------------------------------------------
//
// Layout variables
//
//--------------------------------------------------------------------------
/**
* @private
*/
private var borderSize:uint;
/**
* Default height for navigationGroup, titleGroup and actionGroup.
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
protected var layoutContentGroupHeight:uint;
/**
* Default horizontal padding for the titleGroup and titleDisplay.
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
protected var layoutTitleGroupHorizontalPadding:uint;
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* @copy spark.skins.spark.ApplicationSkin#hostComponent
*/
public var hostComponent:ActionBar;
/**
* @private
*/
private var _navigationVisible:Boolean = false;
/**
* @private
*/
private var _titleContentVisible:Boolean = false;
/**
* @private
*/
private var _actionVisible:Boolean = false;
/**
* @private
*/
private var border:SpriteVisualElement;
//--------------------------------------------------------------------------
//
// Skin parts
//
//--------------------------------------------------------------------------
/**
* @copy spark.components.ActionBar#navigationGroup
*/
public var navigationGroup:Group;
/**
* @copy spark.components.ActionBar#titleGroup
*/
public var titleGroup:Group;
/**
* @copy spark.components.ActionBar#actionGroup
*/
public var actionGroup:Group;
/**
* @copy spark.components.ActionBar#titleDisplay
*
* @private
* Wraps a StyleableTextField in a UIComponent to be compatible with
* ViewTransition effects.
*/
public var titleDisplay:TitleDisplayComponent;
//--------------------------------------------------------------------------
//
// Overridden methods
//
//--------------------------------------------------------------------------
/**
* @private
*/
override protected function createChildren():void
{
if (borderClass)
{
border = new borderClass();
addChild(border);
}
navigationGroup = new Group();
var hLayout:HorizontalLayout = new HorizontalLayout();
hLayout.horizontalAlign = HorizontalAlign.LEFT;
hLayout.verticalAlign = VerticalAlign.MIDDLE;
hLayout.gap = 0;
hLayout.paddingLeft = hLayout.paddingTop = hLayout.paddingRight =
hLayout.paddingBottom = 0;
navigationGroup.layout = hLayout;
navigationGroup.id = "navigationGroup";
titleGroup = new Group();
hLayout = new HorizontalLayout();
hLayout.horizontalAlign = HorizontalAlign.LEFT;
hLayout.verticalAlign = VerticalAlign.MIDDLE;
hLayout.gap = 0;
hLayout.paddingLeft = hLayout.paddingRight = layoutTitleGroupHorizontalPadding;
hLayout.paddingTop = hLayout.paddingBottom = 0;
titleGroup.layout = hLayout;
titleGroup.id = "titleGroup";
actionGroup = new Group();
hLayout = new HorizontalLayout();
hLayout.horizontalAlign = HorizontalAlign.RIGHT;
hLayout.verticalAlign = VerticalAlign.MIDDLE;
hLayout.gap = 0;
hLayout.paddingLeft = hLayout.paddingTop = hLayout.paddingRight =
hLayout.paddingBottom = 0;
actionGroup.layout = hLayout;
actionGroup.id = "actionGroup";
titleDisplay = new TitleDisplayComponent();
titleDisplay.id = "titleDisplay";
// initialize titleAlign style (center is managed explicitly in layoutContents)
var titleAlign:String = getStyle("titleAlign");
titleAlign = (titleAlign == "center") ? TextFormatAlign.LEFT : titleAlign;
titleDisplay.setStyle("textAlign", titleAlign);
addChild(navigationGroup);
addChild(titleGroup);
addChild(actionGroup);
addChild(titleDisplay);
}
/**
* @private
*/
override protected function measure():void
{
var titleWidth:Number = 0;
var titleHeight:Number = 0;
if (_titleContentVisible)
{
titleWidth = titleGroup.getPreferredBoundsWidth();
titleHeight = titleGroup.getPreferredBoundsHeight();
}
else
{
// use titleLayout for paddingLeft and paddingRight
var layoutObject:Object = hostComponent.titleLayout;
titleWidth = ((layoutObject.paddingLeft) ? Number(layoutObject.paddingLeft) : 0)
+ ((layoutObject.paddingRight) ? Number(layoutObject.paddingRight) : 0)
+ titleDisplay.getPreferredBoundsWidth();
titleHeight = titleDisplay.getPreferredBoundsHeight();
}
measuredWidth =
getStyle("paddingLeft")
+ navigationGroup.getPreferredBoundsWidth()
+ titleWidth
+ actionGroup.getPreferredBoundsWidth()
+ getStyle("paddingRight");
// measuredHeight is contentGroupHeight, 2x border on top and bottom
measuredHeight =
getStyle("paddingTop")
+ Math.max(layoutContentGroupHeight,
navigationGroup.getPreferredBoundsHeight(),
actionGroup.getPreferredBoundsHeight(),
titleHeight)
+ getStyle("paddingBottom");
}
/**
* @private
*/
override protected function commitCurrentState():void
{
super.commitCurrentState();
_titleContentVisible = currentState.indexOf("titleContent") >= 0;
_navigationVisible = currentState.indexOf("Navigation") >= 0;
_actionVisible = currentState.indexOf("Action") >= 0;
invalidateSize();
invalidateDisplayList();
}
/**
* @private
*/
override public function styleChanged(styleProp:String):void
{
if (titleDisplay)
{
var allStyles:Boolean = !styleProp || styleProp == "styleName";
if (allStyles || (styleProp == "titleAlign"))
{
var titleAlign:String = getStyle("titleAlign");
if (titleAlign == "center")
{
// If the title align is set to center, the alignment is set to LEFT
// so that the skin can manually center the component in layoutContents
titleDisplay.setStyle("textAlign", TextFormatAlign.LEFT);
}
else
{
titleDisplay.setStyle("textAlign", titleAlign);
}
}
}
super.styleChanged(styleProp);
}
/**
* @private
*/
override protected function layoutContents(unscaledWidth:Number, unscaledHeight:Number):void
{
super.layoutContents(unscaledWidth, unscaledHeight);
var navigationGroupWidth:Number = 0;
var paddingLeft:Number = getStyle("paddingLeft");
var paddingRight:Number = getStyle("paddingRight");
var paddingTop:Number = getStyle("paddingTop");
var paddingBottom:Number = getStyle("paddingBottom");
var titleCompX:Number = paddingLeft;
var titleCompWidth:Number = 0;
var actionGroupX:Number = unscaledWidth;
var actionGroupWidth:Number = 0;
// remove top and bottom padding from content group height
var contentGroupsHeight:Number = Math.max(0, unscaledHeight - paddingTop - paddingBottom);
if (border)
{
// FXG uses scale-9, drop shadow is drawn outside the bounds
setElementSize(border, unscaledWidth, unscaledHeight);
}
// position groups, overlap of navigation and action groups is allowed
// when overlap occurs, titleDisplay/titleGroup is not visible
if (_navigationVisible)
{
navigationGroupWidth = navigationGroup.getPreferredBoundsWidth();
titleCompX += navigationGroupWidth;
setElementSize(navigationGroup, navigationGroupWidth, contentGroupsHeight);
setElementPosition(navigationGroup, paddingLeft, paddingTop);
}
if (_actionVisible)
{
// actionGroup x position can be negative
actionGroupWidth = actionGroup.getPreferredBoundsWidth();
actionGroupX = unscaledWidth - actionGroupWidth - paddingRight;
setElementSize(actionGroup, actionGroupWidth, contentGroupsHeight);
setElementPosition(actionGroup, actionGroupX, paddingTop);
}
// titleGroup or titleDisplay is given remaining width after navigation
// and action groups preferred widths
titleCompWidth = unscaledWidth - navigationGroupWidth - actionGroupWidth
- paddingLeft - paddingRight;
if (titleCompWidth <= 0)
{
titleDisplay.visible = false;
titleGroup.visible = false;
}
else if (_titleContentVisible)
{
titleDisplay.visible = false;
titleGroup.visible = true;
// use titleGroup for titleContent
setElementSize(titleGroup, titleCompWidth, contentGroupsHeight);
setElementPosition(titleGroup, titleCompX, paddingTop);
}
else
{
// use titleDisplay for title text label
titleGroup.visible = false;
// use titleLayout for paddingLeft and paddingRight
var layoutObject:Object = hostComponent.titleLayout;
var titlePaddingLeft:Number = (layoutObject.paddingLeft) ? Number(layoutObject.paddingLeft) : 0;
var titlePaddingRight:Number = (layoutObject.paddingRight) ? Number(layoutObject.paddingRight) : 0;
// align titleDisplay to the absolute center
var titleAlign:String = getStyle("titleAlign");
// check for available width after padding
if ((titleCompWidth - titlePaddingLeft - titlePaddingRight) <= 0)
{
titleCompX = 0;
titleCompWidth = 0;
}
else if (titleAlign == "center")
{
// use LEFT instead of CENTER
titleCompWidth = titleDisplay.getExplicitOrMeasuredWidth();
// use x position of titleDisplay to implement CENTER
titleCompX = Math.round((unscaledWidth - titleCompWidth) / 2);
var navigationOverlap:Number = navigationGroupWidth + titlePaddingLeft - titleCompX;
var actionOverlap:Number = (titleCompX + titleCompWidth + titlePaddingRight) - actionGroupX;
// shrink and/or move titleDisplay width if there is any
// overlap after centering
if ((navigationOverlap > 0) && (actionOverlap > 0))
{
// remaining width
titleCompX = navigationGroupWidth + titlePaddingLeft;
titleCompWidth = unscaledWidth - navigationGroupWidth - actionGroupWidth - titlePaddingLeft - titlePaddingRight;
}
else if ((navigationOverlap > 0) || (actionOverlap > 0))
{
if (navigationOverlap > 0)
{
// nudge to the right
titleCompX += navigationOverlap;
}
else if (actionOverlap > 0)
{
// nudge to the left
titleCompX -= actionOverlap;
// force left padding
if (titleCompX < (navigationGroupWidth + titlePaddingLeft))
titleCompX = navigationGroupWidth + titlePaddingLeft;
}
// recompute action overlap and force right padding
actionOverlap = (titleCompX + titleCompWidth + titlePaddingRight) - actionGroupX;
if (actionOverlap > 0)
titleCompWidth -= actionOverlap;
}
}
else
{
// implement padding by adjusting width and position
titleCompX += titlePaddingLeft;
titleCompWidth = titleCompWidth - titlePaddingLeft - titlePaddingRight;
}
// check for negative width
titleCompWidth = (titleCompWidth < 0) ? 0 : titleCompWidth;
setElementSize(titleDisplay, titleCompWidth, contentGroupsHeight);
setElementPosition(titleDisplay, titleCompX, paddingTop);
titleDisplay.visible = true;
}
}
/**
* @private
*/
override protected function drawBackground(unscaledWidth:Number, unscaledHeight:Number):void
{
super.drawBackground(unscaledWidth, unscaledHeight);
var chromeColor:uint = getStyle("chromeColor");
var backgroundAlphaValue:Number = getStyle("backgroundAlpha");
var colors:Array = [];
// apply alpha to chromeColor fill only
var backgroundAlphas:Array = [backgroundAlphaValue, backgroundAlphaValue];
// exclude top and bottom 1px borders
colorMatrix.createGradientBox(unscaledWidth, unscaledHeight - (borderSize * 2), Math.PI / 2, 0, 0);
colors[0] = ColorUtil.adjustBrightness2(chromeColor, 20);
colors[1] = chromeColor;
graphics.beginGradientFill(GradientType.LINEAR, colors, backgroundAlphas, ACTIONBAR_CHROME_COLOR_RATIOS, colorMatrix);
graphics.drawRect(0, borderSize, unscaledWidth, unscaledHeight - (borderSize * 2));
graphics.endFill();
}
}
}
import flash.events.Event;
import mx.core.UIComponent;
import mx.core.mx_internal;
import mx.events.FlexEvent;
import spark.components.supportClasses.StyleableTextField;
import spark.core.IDisplayText;
use namespace mx_internal;
/**
* @private
* Component that holds StyleableTextFields to produce a drop shadow effect.
* Combines label and shadow into a single component to allow transitions to
* target them both.
*/
class TitleDisplayComponent extends UIComponent implements IDisplayText
{
private var titleDisplay:StyleableTextField;
private var titleDisplayShadow:StyleableTextField;
private var title:String;
private var titleChanged:Boolean;
public function TitleDisplayComponent()
{
super();
title = "";
}
override public function get baselinePosition():Number
{
return titleDisplay.baselinePosition;
}
/**
* @private
*/
override protected function createChildren():void
{
super.createChildren();
titleDisplay = StyleableTextField(createInFontContext(StyleableTextField));
titleDisplay.styleName = this;
titleDisplay.editable = false;
titleDisplay.selectable = false;
titleDisplay.multiline = false;
titleDisplay.wordWrap = false;
titleDisplay.addEventListener(FlexEvent.VALUE_COMMIT,
titleDisplay_valueCommitHandler);
titleDisplayShadow =
StyleableTextField(createInFontContext(StyleableTextField));
titleDisplayShadow.styleName = this;
titleDisplayShadow.colorName = "textShadowColor";
titleDisplayShadow.editable = false;
titleDisplayShadow.selectable = false;
titleDisplayShadow.multiline = false;
titleDisplayShadow.wordWrap = false;
addChild(titleDisplayShadow);
addChild(titleDisplay);
}
/**
* @private
*/
override protected function commitProperties():void
{
super.commitProperties();
if (titleChanged)
{
titleDisplay.text = title;
invalidateSize();
invalidateDisplayList();
titleChanged = false;
}
}
/**
* @private
*/
override protected function measure():void
{
// reset text if it was truncated before.
if (titleDisplay.isTruncated)
titleDisplay.text = title;
measuredWidth = titleDisplay.getPreferredBoundsWidth();
// tightTextHeight
measuredHeight = titleDisplay.getPreferredBoundsHeight();
}
/**
* @private
*/
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
// reset text if it was truncated before.
if (titleDisplay.isTruncated)
titleDisplay.text = title;
titleDisplay.commitStyles();
// use preferred height, setLayoutBoundsSize will accommodate for tight
// text adjustment
var tightHeight:Number = titleDisplay.getPreferredBoundsHeight();
var tightY:Number = (unscaledHeight - tightHeight) / 2;
titleDisplay.setLayoutBoundsSize(unscaledWidth, tightHeight);
titleDisplay.setLayoutBoundsPosition(0, (unscaledHeight - tightHeight) / 2);
// now truncate the text
titleDisplay.truncateToFit();
titleDisplayShadow.commitStyles();
titleDisplayShadow.setLayoutBoundsSize(unscaledWidth, tightHeight);
titleDisplayShadow.setLayoutBoundsPosition(0, tightY + 1);
titleDisplayShadow.alpha = getStyle("textShadowAlpha");
// if labelDisplay is truncated, then push it down here as well.
// otherwise, it would have gotten pushed in the labelDisplay_valueCommitHandler()
if (titleDisplay.isTruncated)
titleDisplayShadow.text = titleDisplay.text;
}
/**
* @private
*/
private function titleDisplay_valueCommitHandler(event:Event):void
{
titleDisplayShadow.text = titleDisplay.text;
}
public function get text():String
{
return title;
}
public function set text(value:String):void
{
title = value;
titleChanged = true;
invalidateProperties();
}
public function get isTruncated():Boolean
{
return titleDisplay.isTruncated;
}
}