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