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
