You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@flex.apache.org by jm...@apache.org on 2013/10/08 17:22:49 UTC

[10/25] FIXED https://issues.apache.org/jira/browse/FLEX-33350 - mobile Callout moved to spark - Added borderColor and borderThickness styles to Callout - Desktop & Mobile Callout uses same s:Callout - MobileSkin refactored - new ActionScriptSkinBase

http://git-wip-us.apache.org/repos/asf/flex-sdk/blob/074354ca/frameworks/projects/spark/src/spark/skins/spark/CalloutSkin.as
----------------------------------------------------------------------
diff --git a/frameworks/projects/spark/src/spark/skins/spark/CalloutSkin.as b/frameworks/projects/spark/src/spark/skins/spark/CalloutSkin.as
new file mode 100644
index 0000000..d3b1357
--- /dev/null
+++ b/frameworks/projects/spark/src/spark/skins/spark/CalloutSkin.as
@@ -0,0 +1,743 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.spark
+{
+
+import flash.display.BlendMode;
+import flash.display.GradientType;
+import flash.display.Graphics;
+import flash.display.Sprite;
+import flash.events.Event;
+
+import mx.core.UIComponent;
+import mx.core.mx_internal;
+import mx.events.EffectEvent;
+import mx.events.FlexEvent;
+import mx.utils.ColorUtil;
+
+import spark.components.ArrowDirection;
+import spark.components.Callout;
+import spark.components.ContentBackgroundAppearance;
+import spark.components.Group;
+import spark.core.SpriteVisualElement;
+import spark.effects.Fade;
+import spark.primitives.RectangularDropShadow;
+import spark.skins.ActionScriptSkinBase;
+import spark.skins.spark.assets.CalloutContentBackground;
+import spark.skins.spark.supportClasses.CalloutArrow;
+
+
+use namespace mx_internal;
+
+/**
+ *  The default skin class for the Spark Callout component in mobile
+ *  applications.
+ * 
+ *  <p>The <code>contentGroup</code> lies above a <code>backgroundColor</code> fill
+ *  which frames the <code>contentGroup</code>. The position and size of the frame 
+ *  adjust based on the host component <code>arrowDirection</code>, leaving
+ *  space for the <code>arrow</code> to appear on the outside edge of the
+ *  frame.</p>
+ * 
+ *  <p>The <code>arrow</code> skin part is not positioned by the skin. Instead,
+ *  the Callout component positions the arrow relative to the owner in
+ *  <code>updateSkinDisplayList()</code>. This method assumes that Callout skin
+ *  and the <code>arrow</code> use the same coordinate space.</p>
+ *  
+ *  @see spark.components.Callout
+ *  
+ *  @langversion 3.0
+ *  @playerversion AIR 3
+ *  @productversion Flex 4.6
+ */ 
+public class CalloutSkin extends ActionScriptSkinBase
+{
+    mx_internal static const BACKGROUND_GRADIENT_BRIGHTNESS_TOP:int = 15;
+    
+    mx_internal static const BACKGROUND_GRADIENT_BRIGHTNESS_BOTTOM:int = -60;
+    
+    //--------------------------------------------------------------------------
+    //
+    //  Constructor
+    //
+    //--------------------------------------------------------------------------
+    
+    /**
+     *  Constructor. 
+     * 
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    public function CalloutSkin()
+    {
+        super();
+        
+        dropShadowAlpha = 0.7;
+
+                // default DPI_160
+        backgroundCornerRadius = 6;
+        contentBackgroundInsetClass = CalloutContentBackground;
+        backgroundGradientHeight = 83;
+        frameThickness = 6;
+        arrowWidth = 26;
+        arrowHeight = 13;
+        contentCornerRadius = 4;
+        dropShadowBlurX = 12;
+        dropShadowBlurY = 12;
+        dropShadowDistance = 2;
+        highlightWeight = 0.5;
+
+         /*
+                backgroundCornerRadius = 8;
+                contentBackgroundInsetClass = CalloutContentBackground;
+                backgroundGradientHeight = 110;
+                frameThickness = 6;
+                arrowWidth = 26;
+                arrowHeight = 13;
+                contentCornerRadius = 5;
+                dropShadowBlurX = 16;
+                dropShadowBlurY = 16;
+                dropShadowDistance = 3;
+                highlightWeight = 1;
+          */
+    }
+    
+    //--------------------------------------------------------------------------
+    //
+    //  Variables
+    //
+    //--------------------------------------------------------------------------
+    
+    /** 
+     *  @copy spark.skins.spark.ApplicationSkin#hostComponent
+     */
+    public var hostComponent:Callout;
+    
+    /**
+     *  Enables a RectangularDropShadow behind the <code>backgroundColor</code> frame.
+     *  
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    protected var dropShadowVisible:Boolean = true;
+    
+    /**
+     *  Enables a vertical linear gradient in the <code>backgroundColor</code> frame. This
+     *  gradient fill is drawn across both the arrow and the frame. By default,
+     *  the gradient brightens the background color by 15% and darkens it by 60%.
+     * 
+     *  @default true
+     *  
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    protected var useBackgroundGradient:Boolean = true;
+    
+    /**
+     *  Corner radius used for the <code>contentBackgroundColor</code> fill.
+     *  
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    protected var contentCornerRadius:uint;
+    
+    /**
+     *  A class reference to an FXG class that is layered underneath the
+     *  <code>contentGroup</code>. The instance of this class is sized to match the
+     *  <code>contentGroup</code>.
+     *  
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    protected var contentBackgroundInsetClass:Class;
+    
+    /**
+     *  Corner radius of the <code>backgroundColor</code> "frame".
+     *  
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    protected var backgroundCornerRadius:Number;
+    
+    /**
+     *  The thickness of the <code>backgroundColor</code> "frame" that surrounds the
+     *  <code>contentGroup</code>.
+     *  
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    protected var frameThickness:Number;
+    
+    /**
+     *  Color of the border stroke around the <code>backgroundColor</code> "frame".
+     *
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    mx_internal var borderColor:uint  = 0;
+
+    /**
+     *  Thickness of the border stroke around the <code>backgroundColor</code>
+     *  "frame".
+     *
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    mx_internal var borderThickness:Number = NaN;
+
+    /**
+     *  Width of the arrow in vertical directions. This property also controls
+     *  the height of the arrow in horizontal directions.
+     *  
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    protected var arrowWidth:Number;
+    
+    /**
+     *  Height of the arrow in vertical directions. This property also controls
+     *  the width of the arrow in horizontal directions.
+     *  
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    protected var arrowHeight:Number;
+    
+    /**
+     *  @private
+     *  Instance of the contentBackgroundClass
+     */
+    mx_internal var contentBackgroundGraphic:SpriteVisualElement;
+    
+    /**
+     *  @private
+     *  Tracks changes to the skin state to support the fade out tranisition 
+     *  when closed;
+     */
+    mx_internal var isOpen:Boolean;
+    
+    private var backgroundGradientHeight:Number;
+    
+    private var contentMask:Sprite;
+    
+    private var backgroundFill:SpriteVisualElement;
+    
+    private var dropShadow:RectangularDropShadow;
+    
+    private var dropShadowBlurX:Number;
+    
+    private var dropShadowBlurY:Number;
+    
+    private var dropShadowDistance:Number;
+    
+    private var dropShadowAlpha:Number;
+    
+    private var fade:Fade;
+    
+    private var highlightWeight:Number;
+    
+    //--------------------------------------------------------------------------
+    //
+    //  Skin parts
+    //
+    //--------------------------------------------------------------------------
+    
+    /**
+     *  @copy spark.components.SkinnableContainer#contentGroup
+     */
+    public var contentGroup:Group;
+    
+    /**
+     * @copy spark.components.Callout#arrow
+     */
+    public var arrow:UIComponent;
+
+    
+    //--------------------------------------------------------------------------
+    //
+    //  Overridden methods
+    //
+    //--------------------------------------------------------------------------
+    
+    /**
+     * @private
+     */
+    override protected function createChildren():void
+    {
+        super.createChildren();
+        
+        if (dropShadowVisible)
+        {
+            dropShadow = new RectangularDropShadow();
+            dropShadow.angle = 90;
+            dropShadow.distance = dropShadowDistance;
+            dropShadow.blurX = dropShadowBlurX;
+            dropShadow.blurY = dropShadowBlurY;
+            dropShadow.tlRadius = dropShadow.trRadius = dropShadow.blRadius = 
+                dropShadow.brRadius = backgroundCornerRadius;
+            dropShadow.mouseEnabled = false;
+            dropShadow.alpha = dropShadowAlpha;
+            addChild(dropShadow);
+        }
+        
+        // background fill placed above the drop shadow
+        backgroundFill = new SpriteVisualElement();
+        addChild(backgroundFill);
+        
+        // arrow
+        if (!arrow)
+        {
+            arrow = new CalloutArrow();
+            arrow.id = "arrow";
+            arrow.styleName = this;
+            addChild(arrow);
+        }
+        
+        // contentGroup
+        if (!contentGroup)
+        {
+            contentGroup = new Group();
+            contentGroup.id = "contentGroup";
+            addChild(contentGroup);
+        }
+
+        borderThickness = getStyle("borderThickness");
+        borderColor = getStyle("borderColor");
+
+    }
+    
+    /**
+     * @private
+     */
+    override protected function commitProperties():void
+    {
+        super.commitProperties();
+        
+        // add or remove the contentBackgroundGraphic
+        var contentBackgroundAppearance:String = getStyle("contentBackgroundAppearance");
+        
+        if (contentBackgroundAppearance == ContentBackgroundAppearance.INSET)
+        {
+            // create the contentBackgroundGraphic
+            if (!contentBackgroundGraphic && contentBackgroundInsetClass)
+            {
+                contentBackgroundGraphic = new contentBackgroundInsetClass() as SpriteVisualElement;
+                
+                // with the current skin structure, contentBackgroundGraphic is
+                // always the last child
+                addChild(contentBackgroundGraphic);
+            }
+        }
+        else if (contentBackgroundGraphic)
+        {
+            // if already created, remove the graphic for "flat" and "none"
+            removeChild(contentBackgroundGraphic);
+            contentBackgroundGraphic = null;
+        }
+        
+        // always invalidate to accomodate arrow direction changes
+        invalidateSize();
+        invalidateDisplayList();
+    }
+    
+    /**
+     * @private
+     */
+    override protected function measure():void
+    {
+        super.measure();
+
+        var borderWeight:Number = isNaN(borderThickness) ? 0 : borderThickness;
+        var frameAdjustment:Number = (frameThickness + borderWeight) * 2;
+        
+        var arrowMeasuredWidth:Number;
+        var arrowMeasuredHeight:Number;
+        
+        // pad the arrow so that the edges are within the background corner radius
+        if (isArrowHorizontal)
+        {
+            arrowMeasuredWidth = arrowHeight;
+            arrowMeasuredHeight = arrowWidth + (backgroundCornerRadius * 2);
+        }
+        else if (isArrowVertical)
+        {
+            arrowMeasuredWidth = arrowWidth + (backgroundCornerRadius * 2);
+            arrowMeasuredHeight = arrowHeight;
+        }
+        
+        // count the contentGroup size and frame size
+        measuredMinWidth = contentGroup.measuredMinWidth + frameAdjustment;
+        measuredMinHeight = contentGroup.measuredMinHeight + frameAdjustment;
+        
+        measuredWidth = contentGroup.getPreferredBoundsWidth() + frameAdjustment;
+        measuredHeight = contentGroup.getPreferredBoundsHeight() + frameAdjustment;
+        
+        // add the arrow size based on the arrowDirection
+        if (isArrowHorizontal)
+        {
+            measuredMinWidth += arrowMeasuredWidth;
+            measuredMinHeight = Math.max(measuredMinHeight, arrowMeasuredHeight);
+            
+            measuredWidth += arrowMeasuredWidth;
+            measuredHeight = Math.max(measuredHeight, arrowMeasuredHeight);
+        }
+        else if (isArrowVertical)
+        {
+            measuredMinWidth += Math.max(measuredMinWidth, arrowMeasuredWidth);
+            measuredMinHeight += arrowMeasuredHeight;
+            
+            measuredWidth = Math.max(measuredWidth, arrowMeasuredWidth);
+            measuredHeight += arrowMeasuredHeight;
+        }
+    }
+    
+    /**
+     *  @private
+     *  SkinnaablePopUpContainer skins must dispatch a 
+     *  FlexEvent.STATE_CHANGE_COMPLETE event for the component to properly
+     *  update the skin state.
+     */
+    override protected function commitCurrentState():void
+    {
+        super.commitCurrentState();
+        
+        var isNormal:Boolean = (currentState == "normal");
+        var isDisabled:Boolean = (currentState == "disabled")
+        
+        // play a fade out if the callout was previously open
+        if (!(isNormal || isDisabled) && isOpen)
+        {
+            if (!fade)
+            {
+                fade = new Fade();
+                fade.target = this;
+                fade.duration = 200;
+                fade.alphaTo = 0;
+            }
+            
+            // BlendMode.LAYER while fading out
+            blendMode = BlendMode.LAYER;
+            
+            // play a short fade effect
+            fade.addEventListener(EffectEvent.EFFECT_END, stateChangeComplete);
+            fade.play();
+            
+            isOpen = false;
+        }
+        else
+        {
+            isOpen = isNormal || isDisabled;
+            
+            // handle re-opening the Callout while fading out
+            if (fade && fade.isPlaying)
+            {
+                // Do not dispatch a state change complete.
+                // SkinnablePopUpContainer handles state interruptions.
+                fade.removeEventListener(EffectEvent.EFFECT_END, stateChangeComplete);
+                fade.stop();
+            }
+            
+            if (isDisabled)
+            {
+                // BlendMode.LAYER to allow CalloutArrow BlendMode.ERASE
+                blendMode = BlendMode.LAYER;
+                
+                alpha = 0.5;
+            }
+            else
+            {
+                // BlendMode.NORMAL for non-animated state transitions
+                blendMode = BlendMode.NORMAL;
+                
+                if (isNormal)
+                    alpha = 1;
+                else
+                    alpha = 0;
+            }
+            
+            stateChangeComplete();
+        }
+    }
+    
+    /**
+     * @private
+     */
+    override protected function drawBackground(unscaledWidth:Number, unscaledHeight:Number):void
+    {
+        super.drawBackground(unscaledWidth, unscaledHeight);
+        
+        var frameEllipseSize:Number = backgroundCornerRadius * 2;
+        
+        // account for borderThickness center stroke alignment
+        var showBorder:Boolean = !isNaN(borderThickness);
+        var borderWeight:Number = showBorder ? borderThickness : 0;
+        
+        // contentBackgroundGraphic already accounts for the arrow position
+        // use it's positioning instead of recalculating based on unscaledWidth
+        // and unscaledHeight
+        var frameX:Number = Math.floor(contentGroup.getLayoutBoundsX() - frameThickness) - (borderWeight / 2);
+        var frameY:Number = Math.floor(contentGroup.getLayoutBoundsY() - frameThickness) - (borderWeight / 2);
+        var frameWidth:Number = contentGroup.getLayoutBoundsWidth() + (frameThickness * 2) + borderWeight;
+        var frameHeight:Number = contentGroup.getLayoutBoundsHeight() + (frameThickness * 2) + borderWeight;
+        
+        var backgroundColor:Number = getStyle("backgroundColor");
+        var backgroundAlpha:Number = getStyle("backgroundAlpha");
+        
+        var bgFill:Graphics = backgroundFill.graphics;
+        bgFill.clear();
+        
+        if (showBorder)
+            bgFill.lineStyle(borderThickness, borderColor, 1, true);
+        
+        if (useBackgroundGradient)
+        {
+            // top color is brighter if arrowDirection == ArrowDirection.UP
+            var backgroundColorTop:Number = ColorUtil.adjustBrightness2(backgroundColor, 
+                BACKGROUND_GRADIENT_BRIGHTNESS_TOP);
+            var backgroundColorBottom:Number = ColorUtil.adjustBrightness2(backgroundColor, 
+                BACKGROUND_GRADIENT_BRIGHTNESS_BOTTOM);
+            
+            // max gradient height = backgroundGradientHeight
+            colorMatrix.createGradientBox(unscaledWidth, backgroundGradientHeight,
+                Math.PI / 2, 0, 0);
+            
+            bgFill.beginGradientFill(GradientType.LINEAR,
+                [backgroundColorTop, backgroundColorBottom],
+                [backgroundAlpha, backgroundAlpha],
+                [0, 255],
+                colorMatrix);
+        }
+        else
+        {
+            bgFill.beginFill(backgroundColor, backgroundAlpha);
+        }
+        
+        bgFill.drawRoundRect(frameX, frameY, frameWidth,
+            frameHeight, frameEllipseSize, frameEllipseSize);
+        bgFill.endFill();
+        
+        // draw content background styles
+        var contentBackgroundAppearance:String = getStyle("contentBackgroundAppearance");
+        
+        if (contentBackgroundAppearance != ContentBackgroundAppearance.NONE)
+        {
+            var contentEllipseSize:Number = contentCornerRadius * 2;
+            var contentBackgroundAlpha:Number = getStyle("contentBackgroundAlpha");
+            var contentWidth:Number = contentGroup.getLayoutBoundsWidth();
+            var contentHeight:Number = contentGroup.getLayoutBoundsHeight();
+            
+            // all appearance values except for "none" use a mask
+            if (!contentMask)
+                contentMask = new SpriteVisualElement();
+            
+            contentGroup.mask = contentMask;
+            
+            // draw contentMask in contentGroup coordinate space
+            var maskGraphics:Graphics = contentMask.graphics;
+            maskGraphics.clear();
+            maskGraphics.beginFill(0, 1);
+            maskGraphics.drawRoundRect(0, 0, contentWidth, contentHeight,
+                contentEllipseSize, contentEllipseSize);
+            maskGraphics.endFill();
+            
+            // reset line style to none
+            if (showBorder)
+                bgFill.lineStyle(NaN);
+            
+            // draw the contentBackgroundColor
+            bgFill.beginFill(getStyle("contentBackgroundColor"),
+                contentBackgroundAlpha);
+            bgFill.drawRoundRect(contentGroup.getLayoutBoundsX(),
+                contentGroup.getLayoutBoundsY(),
+                contentWidth, contentHeight, contentEllipseSize, contentEllipseSize);
+            bgFill.endFill();
+            
+            if (contentBackgroundGraphic)
+                contentBackgroundGraphic.alpha = contentBackgroundAlpha;
+        }
+        else // if (contentBackgroundAppearance == CalloutContentBackgroundAppearance.NONE))
+        {
+            // remove the mask
+            if (contentMask)
+            {
+                contentGroup.mask = null;
+                contentMask = null;
+            }
+        }
+        
+        // draw highlight in the callout when the arrow is hidden
+        if (useBackgroundGradient && !isArrowHorizontal && !isArrowVertical)
+        {
+            // highlight width spans the callout width minus the corner radius
+            var highlightWidth:Number = frameWidth - frameEllipseSize;
+            var highlightX:Number = frameX + backgroundCornerRadius;
+            var highlightOffset:Number = (highlightWeight * 1.5);
+            
+            // straight line across the top
+            bgFill.lineStyle(highlightWeight, 0xFFFFFF, 0.2 * backgroundAlpha);
+            bgFill.moveTo(highlightX, highlightOffset);
+            bgFill.lineTo(highlightX + highlightWidth, highlightOffset);
+        }
+    }
+    
+    /**
+     * @private
+     */
+    override protected function layoutContents(unscaledWidth:Number, unscaledHeight:Number):void
+    {
+        super.layoutContents(unscaledWidth, unscaledHeight);
+        
+        // pad the arrow so that the edges are within the background corner radius
+        if (isArrowHorizontal)
+        {
+            arrow.width = arrowHeight;
+            arrow.height = arrowWidth + (backgroundCornerRadius * 2);
+        }
+        else if (isArrowVertical)
+        {
+            arrow.width = arrowWidth + (backgroundCornerRadius * 2);
+            arrow.height = arrowHeight;
+        }
+        
+        setElementSize(backgroundFill, unscaledWidth, unscaledHeight);
+        setElementPosition(backgroundFill, 0, 0);
+        
+        var frameX:Number = 0;
+        var frameY:Number = 0;
+        var frameWidth:Number = unscaledWidth;
+        var frameHeight:Number = unscaledHeight;
+        
+        switch (hostComponent.arrowDirection)
+        {
+            case ArrowDirection.UP:
+                frameY = arrow.height;
+                frameHeight -= arrow.height;
+                break;
+            case ArrowDirection.DOWN:
+                frameHeight -= arrow.height;
+                break;
+            case ArrowDirection.LEFT:
+                frameX = arrow.width;
+                frameWidth -= arrow.width;
+                break;
+            case ArrowDirection.RIGHT:
+                frameWidth -= arrow.width;
+                break;
+            default:
+                // no arrow, content takes all available space
+                break;
+        }
+        
+        if (dropShadow)
+        {
+            setElementSize(dropShadow, frameWidth, frameHeight);
+            setElementPosition(dropShadow, frameX, frameY);
+        }
+        
+        // Show frameThickness by inset of contentGroup
+        var borderWeight:Number = isNaN(borderThickness) ? 0 : borderThickness;
+        var contentBackgroundAdjustment:Number = frameThickness + borderWeight;
+        
+        var contentBackgroundX:Number = frameX + contentBackgroundAdjustment;
+        var contentBackgroundY:Number = frameY + contentBackgroundAdjustment;
+        
+        contentBackgroundAdjustment = contentBackgroundAdjustment * 2;
+        var contentBackgroundWidth:Number = frameWidth - contentBackgroundAdjustment;
+        var contentBackgroundHeight:Number = frameHeight - contentBackgroundAdjustment;
+        
+        if (contentBackgroundGraphic)
+        {
+            setElementSize(contentBackgroundGraphic, contentBackgroundWidth, contentBackgroundHeight);
+            setElementPosition(contentBackgroundGraphic, contentBackgroundX, contentBackgroundY);
+        }
+        
+        setElementSize(contentGroup, contentBackgroundWidth, contentBackgroundHeight);
+        setElementPosition(contentGroup, contentBackgroundX, contentBackgroundY);
+        
+        // mask position is in the contentGroup coordinate space
+        if (contentMask)
+            setElementSize(contentMask, contentBackgroundWidth, contentBackgroundHeight);
+    }
+    
+    override public function styleChanged(styleProp:String):void
+    {
+        super.styleChanged(styleProp);
+        
+        var allStyles:Boolean = !styleProp || styleProp == "styleName";
+        
+        if (allStyles || (styleProp == "contentBackgroundAppearance"))
+            invalidateProperties();
+        
+        if (allStyles || (styleProp == "backgroundAlpha"))
+        {
+            var backgroundAlpha:Number = getStyle("backgroundAlpha");
+            
+            // Use BlendMode.LAYER to allow CalloutArrow to erase the dropShadow
+            // when the Callout background is transparent
+            blendMode = (backgroundAlpha < 1) ? BlendMode.LAYER : BlendMode.NORMAL;
+        }
+
+    }
+    
+    /**
+     * @private
+     */
+    mx_internal function get isArrowHorizontal():Boolean
+    {
+        return (hostComponent.arrowDirection == ArrowDirection.LEFT
+            || hostComponent.arrowDirection == ArrowDirection.RIGHT);
+    }
+    
+    /**
+     * @private
+     */
+    mx_internal function get isArrowVertical():Boolean
+    {
+        return (hostComponent.arrowDirection == ArrowDirection.UP
+            || hostComponent.arrowDirection == ArrowDirection.DOWN);
+    }
+    
+    //--------------------------------------------------------------------------
+    //
+    //  Event handlers
+    //
+    //--------------------------------------------------------------------------
+    
+    private function stateChangeComplete(event:Event=null):void
+    {
+        if (fade && event)
+            fade.removeEventListener(EffectEvent.EFFECT_END, stateChangeComplete);
+        
+        // SkinnablePopUpContainer relies on state changes for open and close
+        dispatchEvent(new FlexEvent(FlexEvent.STATE_CHANGE_COMPLETE));
+    }
+}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flex-sdk/blob/074354ca/frameworks/projects/spark/src/spark/skins/spark/assets/CalloutContentBackground.fxg
----------------------------------------------------------------------
diff --git a/frameworks/projects/spark/src/spark/skins/spark/assets/CalloutContentBackground.fxg b/frameworks/projects/spark/src/spark/skins/spark/assets/CalloutContentBackground.fxg
new file mode 100644
index 0000000..8442b96
--- /dev/null
+++ b/frameworks/projects/spark/src/spark/skins/spark/assets/CalloutContentBackground.fxg
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  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.
+
+-->
+
+<Graphic version="2.0" xmlns="http://ns.adobe.com/fxg/2008"
+	scaleGridLeft="12" scaleGridRight="588" scaleGridTop="30" scaleGridBottom="388">
+
+	<!-- invisible fix for scaling -->
+	<Rect x="0" y="399" width="600" height="1">
+		<fill>
+			<SolidColor color="#ffffff" alpha="0"/>
+		</fill>
+	</Rect>
+    
+	<!-- Content Shading Top -->
+	<Rect x="0" y="0" width="600" height="20"
+			topLeftRadiusX="10" topLeftRadiusY="10"
+			topRightRadiusX="10" topRightRadiusY="10">
+		<fill>
+			<LinearGradient  rotation="90">
+				<GradientEntry ratio="0" color="#000000" alpha="0.6"/>
+				<GradientEntry ratio="0.5" color="#000000" alpha="0"/>
+			</LinearGradient>
+		</fill>
+	</Rect>
+		
+	<!-- Content Highlight -->
+	<Rect x="1" y="1" width="598" height="398"
+			radiusX="10" radiusY="10">
+		<stroke>
+			<SolidColorStroke color="#FFFFFF" alpha="0.8"
+								weight="2"/>
+		</stroke>
+	</Rect>
+</Graphic>

http://git-wip-us.apache.org/repos/asf/flex-sdk/blob/074354ca/frameworks/projects/spark/src/spark/skins/spark/supportClasses/CalloutArrow.as
----------------------------------------------------------------------
diff --git a/frameworks/projects/spark/src/spark/skins/spark/supportClasses/CalloutArrow.as b/frameworks/projects/spark/src/spark/skins/spark/supportClasses/CalloutArrow.as
new file mode 100644
index 0000000..825fd1e
--- /dev/null
+++ b/frameworks/projects/spark/src/spark/skins/spark/supportClasses/CalloutArrow.as
@@ -0,0 +1,423 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.spark.supportClasses
+{
+import flash.display.BlendMode;
+import flash.display.GradientType;
+import flash.display.Graphics;
+import flash.display.GraphicsPathCommand;
+import flash.display.Sprite;
+
+import mx.core.DPIClassification;
+import mx.core.FlexGlobals;
+import mx.core.IVisualElement;
+import mx.core.UIComponent;
+import mx.core.mx_internal;
+import mx.utils.ColorUtil;
+
+import spark.components.Application;
+import spark.components.ArrowDirection;
+import spark.components.Callout;
+import spark.skins.ActionScriptSkinBase;
+import spark.skins.spark.CalloutSkin;
+
+use namespace mx_internal;
+
+/**
+ *  The arrow skin part for CalloutSkin.
+ *
+ *  @see spark.skin.mobile.CalloutSkin
+ *
+ *  @langversion 3.0
+ *  @playerversion AIR 3
+ *  @productversion Flex 4.6
+ */
+public class CalloutArrow extends UIComponent
+{
+    public function CalloutArrow()
+    {
+        super();
+
+        useBackgroundGradient = true;
+
+        var applicationDPI:Number = Application(FlexGlobals.topLevelApplication).applicationDPI;
+
+        // Copy DPI-specific values from CalloutSkin
+        switch (applicationDPI)
+        {
+            case DPIClassification.DPI_320:
+            {
+                // Note provisional may need changes
+                gap = 32;
+                backgroundGradientHeight = 440;
+                highlightWeight = 4;
+
+                break;
+            }
+            case DPIClassification.DPI_480:
+            {
+                // Note provisional may need changes
+                gap = 24;
+                backgroundGradientHeight = 330;
+                highlightWeight = 3;
+
+                break;
+            }
+            case DPIClassification.DPI_320:
+            {
+                gap = 16;
+                backgroundGradientHeight = 220;
+                highlightWeight = 2;
+
+                break;
+            }
+            case DPIClassification.DPI_240:
+            {
+                gap = 12;
+                backgroundGradientHeight = 165;
+                highlightWeight = 1;
+
+                break;
+            }
+            case DPIClassification.DPI_120:
+            {
+                // Note provisional may need changes
+                gap = 6;
+                backgroundGradientHeight = 83;
+                highlightWeight = 1;
+
+                break;
+            }
+            default:
+            {
+                // default DPI_160
+                gap = 8;
+                backgroundGradientHeight = 110;
+                highlightWeight = 1;
+
+                break;
+            }
+        }
+    }
+
+    /**
+     *  A gap on the frame-adjacent side of the arrow graphic to avoid
+     *  drawing past the CalloutSkin backgroundCornerRadius.
+     *
+     *  <p>The default implementation matches the gap value with the
+     *  <code>backgroundCornerRadius</code> value in <code>CalloutSkin</code>.</p>
+     *
+     *  @see spark.skins.mobile.CalloutSkin#backgroundCornerRadius
+     *
+     *  @langversion 3.0
+     *  @playerversion AIR 3
+     *  @productversion Flex 4.6
+     */
+    protected var gap:Number;
+    /**
+     *  @copy spark.skins.mobile.CalloutSkin#backgroundGradientHeight
+     */
+    protected var backgroundGradientHeight:Number;
+    /**
+     *  @copy spark.skins.mobile.CalloutSkin#useBackgroundGradient
+     */
+    protected var useBackgroundGradient:Boolean;
+    /**
+     *  @copy spark.skins.mobile.CalloutSkin#highlightWeight
+     */
+    private var highlightWeight:Number;
+    /**
+     *  @private
+     *  A sibling of the arrow used to erase the drop shadow in CalloutSkin
+     */
+    private var eraseFill:Sprite;
+
+    /**
+     * @private
+     */
+    override protected function createChildren():void
+    {
+        super.createChildren();
+
+        // eraseFill has the same position and arrow shape in order to erase
+        // the drop shadow under the arrow when backgroundAlpha < 1
+        eraseFill = new Sprite();
+        eraseFill.blendMode = BlendMode.ERASE;
+
+        // layer eraseFill below the arrow 
+        parent.addChildAt(eraseFill, parent.getChildIndex(this));
+    }
+
+    /**
+     * @private
+     */
+    override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
+    {
+        super.updateDisplayList(unscaledWidth, unscaledHeight);
+
+        graphics.clear();
+        eraseFill.graphics.clear();
+
+        var calloutSkin:Object = parent;
+        var hostComponent:Callout = calloutSkin.hostComponent;
+        var arrowDirection:String = hostComponent.arrowDirection;
+
+        if (arrowDirection == ArrowDirection.NONE)
+            return;
+
+        // when drawing the arrow, compensate for cornerRadius via padding
+        var arrowGraphics:Graphics = this.graphics;
+        var eraseGraphics:Graphics = eraseFill.graphics;
+        var arrowWidth:Number = unscaledWidth;
+        var arrowHeight:Number = unscaledHeight;
+        var arrowX:Number = 0;
+        var arrowY:Number = 0;
+        var arrowTipX:Number = 0;
+        var arrowTipY:Number = 0;
+        var arrowEndX:Number = 0;
+        var arrowEndY:Number = 0;
+
+        var borderThickness:Number = hostComponent.getStyle("borderThickness"); // does not inherit
+        var borderColor:uint = hostComponent.getStyle("borderColor"); // does not inherit
+        var showBorder:Boolean = !isNaN(borderThickness) && borderThickness > 0;
+        var borderWeight:Number = showBorder ? borderThickness : 0;
+
+        var borderHalf:Number = borderWeight / 2;
+        var isHorizontal:Boolean = false;
+
+        if ((arrowDirection == ArrowDirection.LEFT) ||
+                (arrowDirection == ArrowDirection.RIGHT))
+        {
+            isHorizontal = true;
+
+            arrowX = -borderHalf;
+            arrowY = gap;
+            arrowHeight = arrowHeight - (gap * 2);
+
+            arrowTipX = arrowWidth - borderHalf;
+            arrowTipY = arrowY + (arrowHeight / 2);
+
+            arrowEndX = arrowX;
+            arrowEndY = arrowY + arrowHeight;
+
+            // flip coordinates to point left
+            if (arrowDirection == ArrowDirection.LEFT)
+            {
+                arrowX = arrowWidth - arrowX;
+                arrowTipX = arrowWidth - arrowTipX;
+                arrowEndX = arrowWidth - arrowEndX;
+            }
+        }
+        else
+        {
+            arrowX = gap;
+            arrowY = -borderHalf;
+            arrowWidth = arrowWidth - (gap * 2);
+
+            arrowTipX = arrowX + (arrowWidth / 2);
+            arrowTipY = arrowHeight - borderHalf;
+
+            arrowEndX = arrowX + arrowWidth;
+            arrowEndY = arrowY;
+
+            // flip coordinates to point up
+            if (hostComponent.arrowDirection == ArrowDirection.UP)
+            {
+                arrowY = arrowHeight - arrowY;
+                arrowTipY = arrowHeight - arrowTipY;
+                arrowEndY = arrowHeight - arrowEndY;
+            }
+        }
+
+        var commands:Vector.<int> = new Vector.<int>(3, true);
+        commands[0] = GraphicsPathCommand.MOVE_TO;
+        commands[1] = GraphicsPathCommand.LINE_TO;
+        commands[2] = GraphicsPathCommand.LINE_TO;
+
+        var coords:Vector.<Number> = new Vector.<Number>(6, true);
+        coords[0] = arrowX;
+        coords[1] = arrowY;
+        coords[2] = arrowTipX
+        coords[3] = arrowTipY;
+        coords[4] = arrowEndX
+        coords[5] = arrowEndY;
+
+        var backgroundColor:Number = getStyle("backgroundColor");
+        var backgroundAlpha:Number = getStyle("backgroundAlpha");
+
+        if (useBackgroundGradient)
+        {
+            var backgroundColorTop:Number = ColorUtil.adjustBrightness2(backgroundColor,
+                    CalloutSkin.BACKGROUND_GRADIENT_BRIGHTNESS_TOP);
+            var backgroundColorBottom:Number = ColorUtil.adjustBrightness2(backgroundColor,
+                    CalloutSkin.BACKGROUND_GRADIENT_BRIGHTNESS_BOTTOM);
+
+            // translate the gradient based on the arrow position
+            ActionScriptSkinBase.colorMatrix.createGradientBox(unscaledWidth,
+                    backgroundGradientHeight, Math.PI / 2, 0, -getLayoutBoundsY());
+
+            arrowGraphics.beginGradientFill(GradientType.LINEAR,
+                    [backgroundColorTop, backgroundColorBottom],
+                    [backgroundAlpha, backgroundAlpha],
+                    [0, 255],
+                    ActionScriptSkinBase.colorMatrix);
+        }
+        else
+        {
+            arrowGraphics.beginFill(backgroundColor, backgroundAlpha);
+        }
+
+        // cover the adjacent border from the callout frame
+        if (showBorder)
+        {
+            var coverX:Number = 0;
+            var coverY:Number = 0;
+            var coverWidth:Number = 0;
+            var coverHeight:Number = 0;
+
+            switch (arrowDirection)
+            {
+                case ArrowDirection.UP:
+                {
+                    coverX = arrowX;
+                    coverY = arrowY;
+                    coverWidth = arrowWidth;
+                    coverHeight = borderWeight;
+                    break;
+                }
+                case ArrowDirection.DOWN:
+                {
+                    coverX = arrowX;
+                    coverY = -borderWeight;
+                    coverWidth = arrowWidth;
+                    coverHeight = borderWeight;
+                    break;
+                }
+                case ArrowDirection.LEFT:
+                {
+                    coverX = arrowX;
+                    coverY = arrowY;
+                    coverWidth = borderWeight;
+                    coverHeight = arrowHeight;
+                    break;
+                }
+                case ArrowDirection.RIGHT:
+                {
+                    coverX = -borderWeight;
+                    coverY = arrowY;
+                    coverWidth = borderWeight;
+                    coverHeight = arrowHeight;
+                    break;
+                }
+            }
+
+            arrowGraphics.drawRect(coverX, coverY, coverWidth, coverHeight);
+        }
+
+        // erase the drop shadow from the CalloutSkin
+        if (backgroundAlpha < 1)
+        {
+            // move eraseFill to the same position as the arrow
+            eraseFill.x = getLayoutBoundsX()
+            eraseFill.y = getLayoutBoundsY();
+
+            // draw the arrow shape
+            eraseGraphics.beginFill(0, 1);
+            eraseGraphics.drawPath(commands, coords);
+            eraseGraphics.endFill();
+        }
+
+        // draw arrow path
+        if (showBorder)
+        {
+            arrowGraphics.lineStyle(borderThickness, borderColor, 1, true);
+        }
+
+
+        arrowGraphics.drawPath(commands, coords);
+        arrowGraphics.endFill();
+
+        // adjust the highlight position to the origin of the callout
+        var isArrowUp:Boolean = (arrowDirection == ArrowDirection.UP);
+        var offsetY:Number = (isArrowUp) ? unscaledHeight : -getLayoutBoundsY();
+
+        // highlight starts after the backgroundCornerRadius
+        var highlightX:Number = gap - getLayoutBoundsX();
+
+        // highlight Y position is based on the stroke weight 
+        var highlightOffset:Number = (highlightWeight * 1.5);
+        var highlightY:Number = highlightOffset + offsetY;
+
+        // highlight width spans the callout width minus the corner radius
+        var highlightWidth:Number = IVisualElement(calloutSkin).getLayoutBoundsWidth() - (gap * 2);
+
+        if (isHorizontal)
+        {
+            highlightWidth -= arrowWidth;
+
+            if (arrowDirection == ArrowDirection.LEFT)
+                highlightX += arrowWidth;
+        }
+
+        // highlight on the top edge is drawn in the arrow only in the UP direction
+        if (useBackgroundGradient)
+        {
+            if (isArrowUp)
+            {
+                // highlight follows the top edge, including the arrow
+                var rightWidth:Number = highlightWidth - arrowWidth;
+
+                // highlight style
+                arrowGraphics.lineStyle(highlightWeight, 0xFFFFFF, 0.2 * backgroundAlpha);
+
+                // in the arrow coordinate space, the highlightX must be less than 0
+                if (highlightX < 0)
+                {
+                    arrowGraphics.moveTo(highlightX, highlightY);
+                    arrowGraphics.lineTo(arrowX, highlightY);
+
+                    // compute the remaining highlight
+                    rightWidth -= (arrowX - highlightX);
+                }
+
+                // arrow highlight (adjust Y downward)
+                coords[1] = arrowY + highlightOffset;
+                coords[3] = arrowTipY + highlightOffset;
+                coords[5] = arrowEndY + highlightOffset;
+                arrowGraphics.drawPath(commands, coords);
+
+                // right side
+                if (rightWidth > 0)
+                {
+                    arrowGraphics.moveTo(arrowEndX, highlightY);
+                    arrowGraphics.lineTo(arrowEndX + rightWidth, highlightY);
+                }
+            }
+            else
+            {
+                // straight line across the top
+                arrowGraphics.lineStyle(highlightWeight, 0xFFFFFF, 0.2 * backgroundAlpha);
+                arrowGraphics.moveTo(highlightX, highlightY);
+                arrowGraphics.lineTo(highlightX + highlightWidth, highlightY);
+            }
+        }
+    }
+}
+}
\ No newline at end of file