| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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.components |
| { |
| import flash.display.DisplayObject; |
| import flash.display.DisplayObjectContainer; |
| import flash.events.Event; |
| import flash.geom.ColorTransform; |
| import flash.geom.Matrix; |
| import flash.geom.Point; |
| import flash.geom.Rectangle; |
| |
| import mx.core.DPIClassification; |
| import mx.core.FlexGlobals; |
| import mx.core.ILayoutElement; |
| import mx.core.IVisualElement; |
| import mx.core.LayoutDirection; |
| import mx.core.UIComponent; |
| import mx.core.mx_internal; |
| import mx.events.ResizeEvent; |
| import mx.managers.SystemManager; |
| import mx.utils.MatrixUtil; |
| import mx.utils.PopUpUtil; |
| |
| use namespace mx_internal; |
| |
| //-------------------------------------- |
| // Styles |
| //-------------------------------------- |
| |
| /** |
| * Appearance of the <code>contentGroup</code>. |
| * Valid MXML values are <code>inset</code>, |
| * <code>flat</code>, and <code>none</code>. |
| * |
| * <p>In ActionScript, you can use the following constants |
| * to set this property: |
| * <code>ContentBackgroundAppearance.INSET</code>, |
| * <code>ContentBackgroundAppearance.FLAT</code> and |
| * <code>ContentBackgroundAppearance.NONE</code>.</p> |
| * |
| * @default ContentBackgroundAppearance.INSET |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 11 |
| * @playerversion AIR 3 |
| * @productversion Flex 4.6 |
| */ |
| [Style(name="contentBackgroundAppearance", type="String", enumeration="inset,flat,none", inherit="no")] |
| |
| /** |
| * Color of the frame border and arrow outline of the Callout control. |
| * @default 0 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 11 |
| * @playerversion AIR 3.8 |
| * @productversion Flex 4.11 |
| */ |
| |
| [Style(name="borderColor", type="uint", format="Color", inherit="no", theme="spark, mobile")] |
| |
| /** |
| * Thickness of the border stroke around the <code>backgroundColor</code> Callout "frame" and arrow. |
| * Set to NaN or 0 to hide the border ; |
| * |
| * @default NaN |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 11 |
| * @playerversion AIR 3.8 |
| * @productversion Flex 4.11 |
| */ |
| [Style(name="borderThickness", type="Number", format="Length", inherit="no", theme="spark,mobile")] |
| |
| //-------------------------------------- |
| // Other metadata |
| //-------------------------------------- |
| |
| [IconFile("Callout.png")] |
| |
| /** |
| * The Callout container is a SkinnablePopUpContainer that functions as a pop-up |
| * with additional owner-relative positioning options similar to PopUpAnchor. |
| * Callout also adds an optional <code>arrow</code> skin part that visually |
| * displays the direction toward the owner. |
| * |
| * |
| * <p>You can also use the CalloutButton control to open a callout container. |
| * The CalloutButton control encapsulates in a single control the callout container |
| * and all of the logic necessary to open and close the callout. |
| * The CalloutButton control is then said to the be the owner, or host, |
| * of the callout.</p> |
| * |
| * <p>Callout uses the <code>horizontalPosition</code> and |
| * <code>verticalPosition</code> properties to determine the position of the |
| * Callout relative to the owner that is specified by the <code>open()</code> |
| * method. |
| * Both properties can be set to <code>CalloutPosition.AUTO</code> which selects a |
| * position based on the aspect ratio of the screen for the Callout to fit |
| * with minimal overlap with the owner and and minimal adjustments at the |
| * screen bounds.</p> |
| * |
| * <p>Once positioned, the Callout positions the arrow on the side adjacent |
| * to the owner, centered as close as possible on the horizontal or vertical |
| * center of the owner as appropriate. The arrow is hidden in cases where |
| * the Callout position is not adjacent to any edge.</p> |
| * |
| * <p>You do not create a Callout container as part of the normal layout |
| * of its parent container. |
| * Instead, it appears as a pop-up container on top of its parent. |
| * Therefore, you do not create it directly in the MXML code of your application.</p> |
| * |
| * <p>Instead, you create is as an MXML component, often in a separate MXML file. |
| * To show the component create an instance of the MXML component, and |
| * then call the <code>open()</code> method. |
| * You can also set the size and position of the component when you open it.</p> |
| * |
| * <p>To close the component, call the <code>close()</code> method. |
| * If the pop-up needs to return data to a handler, you can add an event listener for |
| * the <code>PopUp.CLOSE</code> event, and specify the returned data in |
| * the <code>close()</code> method.</p> |
| * |
| * <p>The Callout is initially in its <code>closed</code> skin state. |
| * When it opens, it adds itself as a pop-up to the PopUpManager, |
| * and transition to the <code>normal</code> skin state. |
| * To define open and close animations, use a custom skin with transitions between |
| * the <code>closed</code> and <code>normal</code> skin states.</p> |
| * |
| * <p>Callout changes the default inheritance behavior seen in Flex components |
| * and instead, inherits styles from the top-level application. This prevents |
| * Callout's contents from unintentionally inheriting styles from an owner |
| * (i.e. Button or TextInput) where the default appearance was desired and |
| * expected.</p> |
| * |
| * <p>The Callout container has the following default characteristics:</p> |
| * <table class="innertable"> |
| * <tr><th>Characteristic</th><th>Description</th></tr> |
| * <tr><td>Default size</td><td>Large enough to display its children</td></tr> |
| * <tr><td>Minimum size</td><td>0 pixels</td></tr> |
| * <tr><td>Maximum size</td><td>10000 pixels wide and 10000 pixels high</td></tr> |
| * <tr><td>Default skin class</td> |
| * <td>spark.skins.mobile.CalloutSkin<br/> |
| * spark.skins.spark.CalloutSkin on desktops</td></tr> |
| * </table> |
| * |
| * @mxml <p>The <code><s:Callout></code> tag inherits all of the tag |
| * attributes of its superclass and adds the following tag attributes:</p> |
| * |
| * <pre> |
| * <s:Callout |
| * <strong>Properties</strong> |
| * horizontalPosition="auto" |
| * verticalPosition="auto" |
| * |
| * <strong>Styles</strong> |
| * contentBackgroundAppearance="inset" |
| * /> |
| * </pre> |
| * |
| * @see spark.components.CalloutButton |
| * @see spark.skins.mobile.CalloutSkin |
| * @see spark.components.ContentBackgroundAppearance |
| * @see spark.components.CalloutPosition |
| * |
| * @includeExample examples/CalloutExample.mxml -noswf |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 11 |
| * * @playerversion AIR 3 |
| * @productversion Flex 4.6 |
| */ |
| public class Callout extends SkinnablePopUpContainer |
| { |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Class constants |
| // |
| //-------------------------------------------------------------------------- |
| |
| private static var decomposition:Vector.<Number> = new <Number>[0,0,0,0,0]; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Constructor |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Constructor. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 11 |
| * @playerversion AIR 3 |
| * @productversion Flex 4.6 |
| */ |
| public function Callout() |
| { |
| super(); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Skin parts |
| // |
| //-------------------------------------------------------------------------- |
| |
| [Bindable] |
| [SkinPart(required="false")] |
| |
| /** |
| * An optional skin part that visually connects the owner to the |
| * contentGroup. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 11 |
| * @playerversion AIR 3 |
| * @productversion Flex 4.6 |
| */ |
| public var arrow:UIComponent; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Variables |
| // |
| //-------------------------------------------------------------------------- |
| |
| private var invalidatePositionFlag:Boolean = false; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Properties |
| // |
| //-------------------------------------------------------------------------- |
| |
| //---------------------------------- |
| // horizontalPosition |
| //---------------------------------- |
| |
| private var _horizontalPosition:String = CalloutPosition.AUTO; |
| |
| [Inspectable(category="General", enumeration="before,start,middle,end,after,auto", defaultValue="auto")] |
| |
| /** |
| * Horizontal position of the callout relative to the owner. |
| * |
| * <p>Possible values are <code>"before"</code>, <code>"start"</code>, |
| * <code>"middle"</code>, <code>"end"</code>, <code>"after"</code>, |
| * and <code>"auto"</code> (default).</p> |
| * |
| * @default CalloutPosition.AUTO |
| * @see spark.components.CalloutPosition |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 11 |
| * @playerversion AIR 3 |
| * @productversion Flex 4.6 |
| */ |
| public function get horizontalPosition():String |
| { |
| return _horizontalPosition; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set horizontalPosition(value:String):void |
| { |
| if (value == _horizontalPosition) |
| return; |
| |
| _horizontalPosition = value; |
| |
| invalidatePosition(); |
| } |
| |
| //---------------------------------- |
| // actualHorizontalPosition |
| //---------------------------------- |
| |
| private var _actualHorizontalPosition:String; |
| |
| /** |
| * Fully resolved horizontal position after evaluating CalloutPosition.AUTO. |
| * |
| * <p>Update this property in <code>commitProperties()</code> when the |
| * explicit <code>horizontalPosition</code> is CalloutPosition.AUTO. |
| * This property must be updated in <code>updatePopUpPosition()</code> |
| * when attempting to reposition the Callout.</p> |
| * |
| * <p>Subclasses should read this property when computing the <code>arrowDirection</code>, |
| * the arrow position in <code>updateSkinDisplayList()</code>.</p> |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 11 |
| * @playerversion AIR 3 |
| * @productversion Flex 4.6 |
| */ |
| mx_internal function get actualHorizontalPosition():String |
| { |
| if (_actualHorizontalPosition) |
| return _actualHorizontalPosition; |
| |
| return horizontalPosition; |
| } |
| |
| /** |
| * @private |
| */ |
| mx_internal function set actualHorizontalPosition(value:String):void |
| { |
| _actualHorizontalPosition = value; |
| } |
| |
| //---------------------------------- |
| // verticalPosition |
| //---------------------------------- |
| |
| private var _verticalPosition:String = CalloutPosition.AUTO; |
| |
| [Inspectable(category="General", enumeration="before,start,middle,end,after,auto", defaultValue="auto")] |
| |
| /** |
| * Vertical position of the callout relative to the owner. |
| * |
| * <p>Possible values are <code>"before"</code>, <code>"start"</code>, |
| * <code>"middle"</code>, <code>"end"</code>, <code>"after"</code>, |
| * and <code>"auto"</code> (default).</p> |
| * |
| * @default CalloutPosition.AUTO |
| * @see spark.components.CalloutPosition |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 11 |
| * @playerversion AIR 3 |
| * @productversion Flex 4.6 |
| */ |
| public function get verticalPosition():String |
| { |
| return _verticalPosition; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set verticalPosition(value:String):void |
| { |
| if (value == _verticalPosition) |
| return; |
| |
| _verticalPosition = value; |
| |
| invalidatePosition(); |
| } |
| |
| //---------------------------------- |
| // actualVerticalPosition |
| //---------------------------------- |
| |
| private var _actualVerticalPosition:String; |
| |
| /** |
| * Fully resolved vertical position after evaluating CalloutPosition.AUTO. |
| * |
| * <p>Update this property in <code>commitProperties()</code> when the |
| * explicit <code>verticalPosition</code> is CalloutPosition.AUTO. |
| * This property must be updated in <code>updatePopUpPosition()</code> |
| * when attempting to reposition the Callout.</p> |
| * |
| * <p>Subclasses should read this property when computing the <code>arrowDirection</code>, |
| * the arrow position in <code>updateSkinDisplayList()</code>.</p> |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 11 |
| * @playerversion AIR 3 |
| * @productversion Flex 4.6 |
| */ |
| mx_internal function get actualVerticalPosition():String |
| { |
| if (_actualVerticalPosition) |
| return _actualVerticalPosition; |
| |
| return verticalPosition; |
| } |
| |
| /** |
| * @private |
| */ |
| mx_internal function set actualVerticalPosition(value:String):void |
| { |
| _actualVerticalPosition = value; |
| } |
| |
| //---------------------------------- |
| // arrowDirection |
| //---------------------------------- |
| |
| private var _arrowDirection:String = ArrowDirection.NONE; |
| |
| /** |
| * @private |
| * Indicates if arrow direction was flipped automatically. |
| */ |
| private var arrowDirectionAdjusted:Boolean = false; |
| |
| /** |
| * A read-only property that indicates the direction from the callout |
| * towards the owner. |
| * |
| * <p>This value is computed based on the callout position given by |
| * <code>horizontalPosition</code> and <code>verticalPosition</code>. |
| * Exterior and interior positions will point from the callout towards |
| * the edge of the owner. Corner and absolute center positions are not |
| * supported and will return a value of <code>"none".</code></p> |
| * |
| * @default none |
| * |
| * @see spark.components.ArrowDirection |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 11 |
| * @playerversion AIR 3 |
| * @productversion Flex 4.6 |
| */ |
| public function get arrowDirection():String |
| { |
| return _arrowDirection; |
| } |
| |
| /** |
| * @private |
| * Invalidate skin when the arrowDirection changes. Dispatches an |
| * "arrowDirectionChanged" event when the property is set. |
| */ |
| mx_internal function setArrowDirection(value:String):void |
| { |
| if (_arrowDirection == value) |
| return; |
| |
| _arrowDirection = value; |
| |
| // Instead of using skin states for each arrowDirection, the |
| // skin must override commitProperties() and account for |
| // arrowDirection on it's own. |
| skin.invalidateProperties(); |
| |
| // adjust margins based on arrow direction |
| switch (arrowDirection) |
| { |
| case ArrowDirection.DOWN: |
| { |
| // Set the marginBottom to zero to place the arrow adjacent to the keyboard |
| softKeyboardEffectMarginBottom = 0; |
| softKeyboardEffectMarginTop = margin; |
| break; |
| } |
| case ArrowDirection.UP: |
| { |
| // Arrow should already be adjacent to the owner or the top of |
| // the screen. |
| softKeyboardEffectMarginTop = 0; |
| softKeyboardEffectMarginBottom = margin; |
| break; |
| } |
| default: |
| { |
| softKeyboardEffectMarginBottom = margin; |
| softKeyboardEffectMarginTop = margin; |
| break; |
| } |
| } |
| |
| if (hasEventListener("arrowDirectionChanged")) |
| dispatchEvent(new Event("arrowDirectionChanged")); |
| } |
| |
| //---------------------------------- |
| // margin |
| //---------------------------------- |
| |
| private var _margin:Number = NaN; |
| |
| /** |
| * @private |
| * Defines a margin around the Callout to nudge it's position away from the |
| * edge of the screen. |
| */ |
| mx_internal function get margin():Number |
| { |
| if (isNaN(_margin)) |
| { |
| var dpi:Number = FlexGlobals.topLevelApplication["applicationDPI"]; |
| |
| if (dpi) |
| { |
| switch (dpi) |
| { |
| case DPIClassification.DPI_640: |
| { |
| _margin = 32; |
| break; |
| } |
| case DPIClassification.DPI_480: |
| { |
| _margin = 24; |
| break; |
| } |
| case DPIClassification.DPI_320: |
| { |
| _margin = 16; |
| break; |
| } |
| case DPIClassification.DPI_240: |
| { |
| _margin = 12; |
| break; |
| } |
| case DPIClassification.DPI_120: |
| { |
| _margin = 6; |
| break; |
| } |
| default: |
| { |
| // default DPI_160 |
| _margin = 8; |
| break; |
| } |
| } |
| } |
| else |
| { |
| _margin = 8; |
| } |
| } |
| |
| return _margin; |
| } |
| |
| private var _explicitMoveForSoftKeyboard:Boolean = false; |
| |
| /** |
| * @private |
| */ |
| override public function get moveForSoftKeyboard():Boolean |
| { |
| // If no explicit setting, then automatically disable move when |
| // pointing up towards the owner. |
| if (!_explicitMoveForSoftKeyboard && |
| (arrowDirection == ArrowDirection.UP)) |
| { |
| return false; |
| } |
| |
| return super.moveForSoftKeyboard; |
| } |
| |
| /** |
| * @private |
| */ |
| override public function set moveForSoftKeyboard(value:Boolean):void |
| { |
| super.moveForSoftKeyboard = value; |
| |
| _explicitMoveForSoftKeyboard = true; |
| } |
| |
| //---------------------------------- |
| // calloutMaxWidth |
| //---------------------------------- |
| |
| private var _calloutMaxWidth:Number = NaN; |
| |
| /** |
| * @private |
| */ |
| mx_internal function get calloutMaxWidth():Number |
| { |
| return _calloutMaxWidth; |
| } |
| |
| /** |
| * @private |
| */ |
| mx_internal function set calloutMaxWidth(value:Number):void |
| { |
| if (_calloutMaxWidth == value) |
| return; |
| |
| _calloutMaxWidth = value; |
| |
| invalidateMaxSize(); |
| } |
| |
| |
| //---------------------------------- |
| // calloutMaxHeight |
| //---------------------------------- |
| |
| private var _calloutMaxHeight:Number = NaN; |
| |
| /** |
| * @private |
| */ |
| mx_internal function get calloutMaxHeight():Number |
| { |
| return _calloutMaxHeight; |
| } |
| |
| /** |
| * @private |
| */ |
| mx_internal function set calloutMaxHeight(value:Number):void |
| { |
| if (_calloutMaxHeight == value) |
| return; |
| |
| _calloutMaxHeight = value; |
| |
| invalidateMaxSize(); |
| } |
| |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Overridden methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| override public function get explicitMaxWidth():Number |
| { |
| if (!isNaN(super.explicitMaxWidth)) |
| return super.explicitMaxWidth; |
| |
| return calloutMaxWidth; |
| } |
| |
| /** |
| * @private |
| */ |
| override public function get explicitMaxHeight():Number |
| { |
| if (!isNaN(super.explicitMaxHeight)) |
| return super.explicitMaxHeight; |
| |
| return calloutMaxHeight; |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function commitProperties():void |
| { |
| super.commitProperties(); |
| |
| // Do not commit position changes if closed (no owner) or owner was |
| // removed from the display list. |
| if (!owner || !owner.parent) |
| return; |
| |
| // Compute actual positions when using AUTO |
| commitAutoPosition(); |
| |
| // Compute max size based on actual positions |
| commitMaxSize(); |
| |
| if (arrow) |
| { |
| // arrowDirection can be set in 2 ways: (1) horizontalPostion/verticalPosition |
| // changes and (2) flipping the axis to fit on screen. |
| if (!arrowDirectionAdjusted) |
| { |
| // Invalidate only when the arrow direction changes |
| var direction:String = determineArrowPosition(actualHorizontalPosition, |
| actualVerticalPosition); |
| |
| if (arrowDirection != direction) |
| { |
| setArrowDirection(direction); |
| |
| if (arrow) |
| arrow.visible = (arrowDirection != ArrowDirection.NONE); |
| } |
| } |
| |
| // Always reset the arrow position |
| invalidateDisplayList(); |
| } |
| } |
| |
| /** |
| * @private |
| * Re-position the pop-up using actualHorizontalPosition and |
| * actualVerticalPosition. |
| */ |
| override public function updatePopUpPosition():void |
| { |
| if (!owner || !systemManager) |
| return; |
| |
| var popUpPoint:Point = calculatePopUpPosition(); |
| var ownerComponent:UIComponent = owner as UIComponent; |
| var concatenatedColorTransform:ColorTransform = |
| (ownerComponent) ? ownerComponent.$transform.concatenatedColorTransform : null; |
| |
| PopUpUtil.applyPopUpTransform(owner, concatenatedColorTransform, |
| systemManager, this, popUpPoint); |
| } |
| |
| /** |
| * @private |
| * |
| * Cooperative layout |
| * @see spark.components.supportClasses.TrackBase#partAdded |
| */ |
| override protected function partAdded(partName:String, instance:Object):void |
| { |
| super.partAdded(partName, instance); |
| |
| if (instance == arrow) |
| arrow.addEventListener(ResizeEvent.RESIZE, arrow_resizeHandler); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function partRemoved(partName:String, instance:Object):void |
| { |
| super.partRemoved(partName, instance); |
| |
| if (instance == arrow) |
| arrow.removeEventListener(ResizeEvent.RESIZE, arrow_resizeHandler); |
| } |
| |
| /** |
| * @private |
| */ |
| override public function open(owner:DisplayObjectContainer, modal:Boolean=false):void |
| { |
| if (isOpen) |
| return; |
| |
| // reset state |
| invalidatePositionFlag = false; |
| arrowDirectionAdjusted = false; |
| |
| // Add to PopUpManager, calls updatePopUpPosition(), and change state |
| super.open(owner, modal); |
| |
| // Reposition the callout when the screen changes |
| var systemManagerParent:SystemManager = this.parent as SystemManager; |
| |
| if (systemManagerParent) |
| systemManagerParent.addEventListener(Event.RESIZE, systemManager_resizeHandler); |
| } |
| |
| /** |
| * @private |
| */ |
| override public function close(commit:Boolean=false, data:*=null):void |
| { |
| if (!isOpen) |
| return; |
| |
| var systemManagerParent:SystemManager = this.parent as SystemManager; |
| |
| if (systemManagerParent) |
| systemManagerParent.removeEventListener(Event.RESIZE, systemManager_resizeHandler); |
| |
| super.close(commit, data); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void |
| { |
| super.updateDisplayList(unscaledWidth, unscaledHeight); |
| |
| // Callout can be respositioned while open via SystemManager resize or |
| // explicit changes to horizontalPostion and verticalPosition. |
| if (isOpen && invalidatePositionFlag) |
| { |
| updatePopUpPosition(); |
| invalidatePositionFlag = false; |
| } |
| |
| // Position the arrow |
| updateSkinDisplayList(); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| private function invalidatePosition():void |
| { |
| arrowDirectionAdjusted = false; |
| |
| invalidateProperties(); |
| |
| if (isOpen) |
| invalidatePositionFlag = true; |
| } |
| |
| /** |
| * @private |
| * Force a new measurement when callout should use it's screen-constrained |
| * max size. |
| */ |
| private function invalidateMaxSize():void |
| { |
| // calloutMaxWidth and calloutMaxHeight don't invalidate |
| // explicitMaxWidth or explicitMaxHeight. If callout's max size changes |
| // and explicit max sizes aren't set, then invalidate size here so that |
| // callout's max size is applied. |
| if (!canSkipMeasurement() && !isMaxSizeSet) |
| skin.invalidateSize(); |
| } |
| |
| /** |
| * Sets the bounds of <code>arrow</code>, whose geometry isn't fully |
| * specified by the skin's layout. |
| * |
| * <p>Subclasses can override this method to update the arrow's size, |
| * position, and visibility, based on the computed |
| * <code>arrowDirection</code>.</p> |
| * |
| * <p>By default, this method aligns the arrow on the shorter of either |
| * the <code>arrow</code> bounds or the <code>owner</code> bounds. This |
| * implementation assumes that the <code>arrow</code> and the Callout skin |
| * share the same coordinate space.</p> |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 11 |
| * @playerversion AIR 3 |
| * @productversion Flex 4.6 |
| */ |
| protected function updateSkinDisplayList():void |
| { |
| var ownerVisualElement:IVisualElement = owner as IVisualElement; |
| |
| // Sanity check to verify owner is still on the display list. If not, |
| // leave the arrow in the current position. |
| if (!arrow || !ownerVisualElement || |
| (arrowDirection == ArrowDirection.NONE) || |
| (!ownerVisualElement.parent)) |
| return; |
| |
| var isStartPosition:Boolean = false; |
| var isMiddlePosition:Boolean = false; |
| var isEndPosition:Boolean = false; |
| |
| var position:String = (isArrowVertical) ? actualHorizontalPosition : actualVerticalPosition; |
| |
| isStartPosition = (position == CalloutPosition.START); |
| isMiddlePosition = (position == CalloutPosition.MIDDLE); |
| isEndPosition = (position == CalloutPosition.END); |
| |
| var isEndOfCallout:Boolean = (arrowDirection == ArrowDirection.DOWN) |
| || (arrowDirection == ArrowDirection.RIGHT); |
| |
| var calloutWidth:Number = getLayoutBoundsWidth(); |
| var calloutHeight:Number = getLayoutBoundsHeight(); |
| var arrowWidth:Number = arrow.getLayoutBoundsWidth(); |
| var arrowHeight:Number = arrow.getLayoutBoundsHeight(); |
| |
| // arrow X/Y in pop-up coordinates |
| var arrowX:Number = 0; |
| var arrowY:Number = 0; |
| |
| // Max arrow positions |
| var maxArrowX:Number = calloutWidth - arrowWidth; |
| var maxArrowY:Number = calloutHeight - arrowHeight; |
| |
| // Find the registration point of the owner |
| var sandboxRoot:DisplayObject = systemManager.getSandboxRoot(); |
| var regPoint:Point = owner.localToGlobal(new Point()); |
| regPoint = sandboxRoot.globalToLocal(regPoint); |
| |
| if (isArrowVertical) |
| { |
| // Vertical arrows need horizontal alignment |
| var ownerX:Number = regPoint.x; |
| var ownerVisibleWidth:Number = (ownerVisualElement) |
| ? ownerVisualElement.getLayoutBoundsWidth() : owner.width; |
| |
| // Edge cases when start/end of owner is not visible |
| if ((ownerX < 0) && (ownerVisibleWidth < screen.width)) |
| ownerVisibleWidth = Math.max(ownerVisibleWidth + ownerX, 0); |
| else if ((ownerX >= 0) && ((ownerX + ownerVisibleWidth) >= screen.width)) |
| ownerVisibleWidth = Math.max(screen.width - ownerX, 0); |
| |
| ownerVisibleWidth = Math.min(ownerVisibleWidth, screen.width); |
| |
| if (calloutWidth <= ownerVisibleWidth) |
| { |
| arrowX = (calloutWidth - arrowWidth) / 2; |
| } |
| else // if (calloutWidth > ownerWidth) |
| { |
| // Center the arrow on the owner |
| arrowX = (ownerVisibleWidth - arrowWidth) / 2; |
| |
| // Add owner offset |
| if (ownerX > 0) |
| arrowX += Math.abs(ownerX - getLayoutBoundsX()); |
| |
| if (ownerX < margin) |
| arrowX -= (margin - ownerX); |
| } |
| |
| // arrow should not extend past the callout bounds |
| arrowX = Math.max(Math.min(maxArrowX, arrowX), 0); |
| |
| // Move the arrow to the bottom of the callout |
| if (isEndOfCallout) |
| arrowY = calloutHeight - arrowHeight; |
| } |
| else |
| { |
| // Horizontal arrows need vertical alignment |
| var ownerY:Number = regPoint.y; |
| var ownerVisibleHeight:Number = (ownerVisualElement) |
| ? ownerVisualElement.getLayoutBoundsHeight() : owner.height; |
| |
| // Edge cases when start/end of owner is not visible |
| if ((ownerY < 0) && (ownerVisibleHeight < screen.height)) |
| ownerVisibleHeight = Math.max(ownerVisibleHeight + ownerY, 0); |
| else if ((ownerY >= 0) && ((ownerY + ownerVisibleHeight) >= screen.height)) |
| ownerVisibleHeight = Math.max(screen.height - ownerY, 0); |
| |
| ownerVisibleHeight = Math.min(ownerVisibleHeight, screen.height); |
| |
| if (calloutHeight <= ownerVisibleHeight) |
| { |
| arrowY = (calloutHeight - arrowHeight) / 2; |
| } |
| else // if (calloutHeight > ownerHeight) |
| { |
| // Center the arrow on the owner |
| arrowY = (ownerVisibleHeight - arrowHeight) / 2; |
| |
| // Add owner offset |
| if (ownerY > 0) |
| arrowY += Math.abs(ownerY - getLayoutBoundsY()); |
| |
| if (ownerY < margin) |
| ownerY -= (margin - ownerY); |
| } |
| |
| // arrow should not extend past the callout bounds |
| arrowY = Math.max(Math.min(maxArrowY, arrowY), 0); |
| |
| // Move the arrow to the end of the callout |
| if (isEndOfCallout) |
| arrowX = calloutWidth - arrowWidth; |
| } |
| |
| arrow.setLayoutBoundsPosition(Math.floor(arrowX), Math.floor(arrowY)); |
| arrow.invalidateDisplayList(); |
| } |
| |
| /** |
| * @private |
| * |
| * Flip or clear the adjusted position when the callout bounds are outside |
| * the screen bounds. |
| */ |
| mx_internal function adjustCalloutPosition(actualPosition:String, preferredPosition:String, |
| calloutStart:Number, calloutEnd:Number, |
| screenStart:Number, screenEnd:Number, |
| ownerStart:Number, ownerEnd:Number, |
| revert:Boolean=false):String |
| { |
| if (!actualPosition) |
| return null; |
| |
| var adjustedPosition:String = null; |
| var calloutSize:Number = calloutEnd - calloutStart; |
| |
| // Exterior space |
| var exteriorSpaceStart:Number = Math.max(0, ownerStart - screenStart); |
| var exteriorSpaceEnd:Number = Math.max(0, ownerEnd - screenEnd); |
| |
| // Fallback to interior positions if using AUTO and callout can not |
| // fit in either exterior positions |
| var useInterior:Boolean = (preferredPosition == CalloutPosition.AUTO) && |
| (exteriorSpaceStart < calloutSize) && |
| (exteriorSpaceEnd < calloutSize); |
| var isExterior:Boolean = false; |
| |
| // Flip to opposite position |
| switch (actualPosition) |
| { |
| case CalloutPosition.BEFORE: |
| { |
| isExterior = true; |
| |
| if (calloutStart < screenStart) |
| adjustedPosition = CalloutPosition.AFTER; |
| |
| break; |
| } |
| case CalloutPosition.AFTER: |
| { |
| isExterior = true; |
| |
| if (calloutEnd > screenEnd) |
| adjustedPosition = CalloutPosition.BEFORE; |
| |
| break; |
| } |
| case CalloutPosition.END: |
| { |
| if (calloutStart < screenStart) |
| adjustedPosition = CalloutPosition.START; |
| break; |
| } |
| case CalloutPosition.START: |
| { |
| if (calloutEnd > screenEnd) |
| adjustedPosition = CalloutPosition.END; |
| break; |
| } |
| // case CalloutPosition.MIDDLE: |
| // Nudge instead of flipping |
| } |
| |
| // Use interior position if exterior flipping was necessary |
| if (useInterior && adjustedPosition && isExterior) |
| { |
| // Choose the exterior position with the most available space. |
| // Note that START grows towards the exterior END and vice versa. |
| adjustedPosition = (exteriorSpaceEnd >= exteriorSpaceStart) ? |
| CalloutPosition.START : CalloutPosition.END; |
| } |
| |
| // Return null to revert the adjusted position |
| // Otherwise, return the incoming position |
| if (revert) |
| return (adjustedPosition) ? null : actualPosition; |
| |
| // Adjusted position or null if the callout already fits |
| return adjustedPosition; |
| } |
| |
| /** |
| * @private |
| * |
| * Nudge the callout position to fit on screen. Prefer top/left positions |
| * and allow overflow to get clipped on the bottom/right. |
| */ |
| mx_internal function nudgeToFit(calloutStart:Number, calloutEnd:Number, |
| screenStart:Number, screenEnd:Number, |
| scaleFactor:Number):Number |
| { |
| var position:Number = 0; |
| |
| if (calloutStart < screenStart) |
| position += (screenStart - calloutStart) / scaleFactor; |
| else if (calloutEnd > screenEnd) |
| position -= (calloutEnd - screenEnd) / scaleFactor; |
| |
| return position; |
| } |
| |
| /** |
| * @private |
| * |
| * Basically the same as PopUpAnchor, but with more position options |
| * including exterior, interior and corner positions. |
| * |
| * Nudging to fit the screen accounts for <code>margin</code> so that |
| * the Callout is not positioned in the margin. |
| * |
| * <code>arrowDirection</code> will change if required for the callout |
| * to fit. |
| * |
| * @see #margin |
| */ |
| mx_internal function calculatePopUpPosition():Point |
| { |
| // This implementation doesn't handle rotation |
| var sandboxRoot:DisplayObject = systemManager.getSandboxRoot(); |
| var matrix:Matrix = MatrixUtil.getConcatenatedMatrix(owner, sandboxRoot); |
| |
| var regPoint:Point = new Point(); |
| |
| if (!matrix) |
| return regPoint; |
| |
| var adjustedHorizontalPosition:String; |
| var adjustedVerticalPosition:String; |
| var calloutBounds:Rectangle = determinePosition(actualHorizontalPosition, |
| actualVerticalPosition, matrix, regPoint); |
| var ownerBounds:Rectangle = owner.getBounds(systemManager.getSandboxRoot()); |
| |
| // Position the callout in the opposite direction if it |
| // does not fit on the screen. |
| if (screen) |
| { |
| adjustedHorizontalPosition = adjustCalloutPosition( |
| actualHorizontalPosition, horizontalPosition, |
| calloutBounds.left, calloutBounds.right, |
| screen.left, screen.right, |
| ownerBounds.left, ownerBounds.right); |
| |
| adjustedVerticalPosition = adjustCalloutPosition( |
| actualVerticalPosition, verticalPosition, |
| calloutBounds.top, calloutBounds.bottom, |
| screen.top, screen.bottom, |
| ownerBounds.top, ownerBounds.bottom); |
| } |
| |
| var oldArrowDirection:String = arrowDirection; |
| var actualArrowDirection:String = null; |
| |
| // Reset arrowDirectionAdjusted |
| arrowDirectionAdjusted = false; |
| |
| // Get the new registration point based on the adjusted position |
| if ((adjustedHorizontalPosition != null) || (adjustedVerticalPosition != null)) |
| { |
| var adjustedRegPoint:Point = new Point(); |
| var tempHorizontalPosition:String = (adjustedHorizontalPosition) |
| ? adjustedHorizontalPosition : actualHorizontalPosition; |
| var tempVerticalPosition:String = (adjustedVerticalPosition) |
| ? adjustedVerticalPosition : actualVerticalPosition; |
| |
| // Adjust arrow direction after adjusting position |
| actualArrowDirection = determineArrowPosition(tempHorizontalPosition, |
| tempVerticalPosition); |
| |
| // All position flips gaurantee an arrowDirection change |
| setArrowDirection(actualArrowDirection); |
| arrowDirectionAdjusted = true; |
| |
| if (arrow) |
| arrow.visible = (arrowDirection != ArrowDirection.NONE); |
| |
| // Reposition the arrow |
| updateSkinDisplayList(); |
| |
| var adjustedBounds:Rectangle = determinePosition(tempHorizontalPosition, |
| tempVerticalPosition, matrix, adjustedRegPoint); |
| |
| if (screen) |
| { |
| // If we adjusted the position but the callout still doesn't fit, |
| // then revert to the original position. |
| adjustedHorizontalPosition = adjustCalloutPosition( |
| adjustedHorizontalPosition, horizontalPosition, |
| adjustedBounds.left, adjustedBounds.right, |
| screen.left, screen.right, |
| ownerBounds.left, ownerBounds.right, |
| true); |
| |
| adjustedVerticalPosition = adjustCalloutPosition( |
| adjustedVerticalPosition, verticalPosition, |
| adjustedBounds.top, adjustedBounds.bottom, |
| screen.top, screen.bottom, |
| ownerBounds.top, ownerBounds.bottom, |
| true); |
| } |
| |
| if ((adjustedHorizontalPosition != null) || (adjustedVerticalPosition != null)) |
| { |
| regPoint = adjustedRegPoint; |
| calloutBounds = adjustedBounds; |
| |
| // Temporarily set actual positions to reposition the arrow |
| if (adjustedHorizontalPosition) |
| actualHorizontalPosition = adjustedHorizontalPosition; |
| |
| if (adjustedVerticalPosition) |
| actualVerticalPosition = adjustedVerticalPosition; |
| |
| // Reposition the arrow with the new actual position |
| updateSkinDisplayList(); |
| } |
| else |
| { |
| // Restore previous arrow direction *before* reversing the |
| // adjusted positions |
| setArrowDirection(oldArrowDirection); |
| arrowDirectionAdjusted = false; |
| |
| // Reposition the arrow to the original position |
| updateSkinDisplayList(); |
| } |
| } |
| |
| MatrixUtil.decomposeMatrix(decomposition, matrix, 0, 0); |
| var concatScaleX:Number = decomposition[3]; |
| var concatScaleY:Number = decomposition[4]; |
| |
| // If the callout still doesn't fit, then nudge it |
| // so it is completely on the screen. Make sure to include scale. |
| var screenTop:Number = screen.top; |
| var screenBottom:Number = screen.bottom; |
| var screenLeft:Number = screen.left; |
| var screenRight:Number = screen.right; |
| |
| // Allow zero margin on the the side with the arrow |
| switch (arrowDirection) |
| { |
| case ArrowDirection.UP: |
| { |
| screenBottom -= margin; |
| screenLeft += margin; |
| screenRight -= margin |
| break; |
| } |
| case ArrowDirection.DOWN: |
| { |
| screenTop += margin; |
| screenLeft += margin; |
| screenRight -= margin |
| break; |
| } |
| case ArrowDirection.LEFT: |
| { |
| screenTop += margin; |
| screenBottom -= margin; |
| screenRight -= margin |
| break; |
| } |
| case ArrowDirection.RIGHT: |
| { |
| screenTop += margin; |
| screenBottom -= margin; |
| screenLeft += margin; |
| break; |
| } |
| default: |
| { |
| screenTop += margin; |
| screenBottom -= margin; |
| screenLeft += margin; |
| screenRight -= margin |
| break; |
| } |
| } |
| |
| regPoint.y += nudgeToFit(calloutBounds.top, calloutBounds.bottom, |
| screenTop, screenBottom, concatScaleY); |
| |
| regPoint.x += nudgeToFit(calloutBounds.left, calloutBounds.right, |
| screenLeft, screenRight, concatScaleX); |
| |
| // Compute the stage coordinates of the upper,left corner of the PopUp, taking |
| // the postTransformOffsets - which include mirroring - into account. |
| // If we're mirroring, then the implicit assumption that x=left will fail, |
| // so we compensate here. |
| |
| if (layoutDirection == LayoutDirection.RTL) |
| regPoint.x += calloutBounds.width; |
| return MatrixUtil.getConcatenatedComputedMatrix(owner, sandboxRoot).transformPoint(regPoint); |
| } |
| |
| /** |
| * @private |
| * Computes <code>actualHorizontalPosition</code> and/or |
| * <code>actualVerticalPosition</code> values when using |
| * <code>CalloutPosition.AUTO</code>. When implementing subclasses of |
| * Callout, use <code>actualHorizontalPosition</code> and |
| * <code>actualVerticalPosition</code> to compute |
| * <code>arrowDirection</code> and positioning in |
| * <code>updatePopUpPosition()</code> and <code>updateSkinDisplayList()</code>. |
| * |
| * <p>The default implementation chooses "outer" positions for the callout |
| * such that the owner is not obscured. Horizontal/Vertical orientation |
| * relative to the owner choosen based on the aspect ratio.</p> |
| * |
| * <p>When the aspect ratio is landscape, and the callout can fit to the |
| * left or right of the owner, <code>actualHorizontalPosition</code> is |
| * set to <code>CalloutPosition.BEFORE</code> or |
| * <code>CalloutPosition.AFTER</code> as appropriate. |
| * <code>actualVerticalPosition</code> is set to |
| * <code>CalloutPosition.MIDDLE</code> to have the vertical center of the |
| * callout align to the vertical center of the owner.</p> |
| * |
| * <p>When the aspect ratio is portrait, and the callout can fit |
| * above or below the owner, <code>actualVerticalPosition</code> is |
| * set to <code>CalloutPosition.BEFORE</code> or |
| * <code>CalloutPosition.AFTER</code> as appropriate. |
| * <code>actualHorizontalPosition</code> is set to |
| * <code>CalloutPosition.MIDDLE</code> to have the horizontal center of the |
| * callout align to the horizontal center of the owner.</p> |
| * |
| * <p>Subclasses may override to modify automatic positioning behavior.</p> |
| */ |
| mx_internal function commitAutoPosition():void |
| { |
| if (!screen || ((horizontalPosition != CalloutPosition.AUTO) && |
| (verticalPosition != CalloutPosition.AUTO))) |
| { |
| // Use explicit positions instead of AUTO |
| actualHorizontalPosition = null; |
| actualVerticalPosition = null; |
| |
| return; |
| } |
| |
| var ownerBounds:Rectangle = owner.getBounds(systemManager.getSandboxRoot()); |
| |
| // Use aspect ratio to determine vertical/horizontal preference |
| var isLandscape:Boolean = (screen.width > screen.height); |
| |
| // Exterior space |
| var exteriorSpaceLeft:Number = Math.max(0, ownerBounds.left); |
| var exteriorSpaceRight:Number = Math.max(0, screen.width - ownerBounds.right); |
| var exteriorSpaceTop:Number = Math.max(0, ownerBounds.top); |
| var exteriorSpaceBottom:Number = Math.max(0, screen.height - ownerBounds.bottom); |
| |
| if (verticalPosition != CalloutPosition.AUTO) |
| { |
| // Horizontal auto only |
| switch (verticalPosition) |
| { |
| case CalloutPosition.START: |
| case CalloutPosition.MIDDLE: |
| case CalloutPosition.END: |
| { |
| actualHorizontalPosition = (exteriorSpaceRight > exteriorSpaceLeft) ? CalloutPosition.AFTER : CalloutPosition.BEFORE; |
| break; |
| } |
| default: |
| { |
| actualHorizontalPosition = CalloutPosition.MIDDLE; |
| break; |
| } |
| } |
| |
| actualVerticalPosition = null; |
| } |
| else if (horizontalPosition != CalloutPosition.AUTO) |
| { |
| // Vertical auto only |
| switch (horizontalPosition) |
| { |
| case CalloutPosition.START: |
| case CalloutPosition.MIDDLE: |
| case CalloutPosition.END: |
| { |
| actualVerticalPosition = (exteriorSpaceBottom > exteriorSpaceTop) ? CalloutPosition.AFTER : CalloutPosition.BEFORE; |
| break; |
| } |
| default: |
| { |
| actualVerticalPosition = CalloutPosition.MIDDLE; |
| break; |
| } |
| } |
| |
| actualHorizontalPosition = null; |
| } |
| else // if ((verticalPosition == CalloutPosition.AUTO) && (horizontalPosition == CalloutPosition.AUTO)) |
| { |
| if (!isLandscape) |
| { |
| // Arrow will be vertical when in portrait |
| actualHorizontalPosition = CalloutPosition.MIDDLE; |
| actualVerticalPosition = (exteriorSpaceBottom > exteriorSpaceTop) ? CalloutPosition.AFTER : CalloutPosition.BEFORE; |
| } |
| else |
| { |
| // Arrow will be horizontal when in landscape |
| actualHorizontalPosition = (exteriorSpaceRight > exteriorSpaceLeft) ? CalloutPosition.AFTER : CalloutPosition.BEFORE; |
| actualVerticalPosition = CalloutPosition.MIDDLE; |
| } |
| } |
| } |
| |
| /** |
| * @private |
| * Return true if user-specified max size if set |
| */ |
| mx_internal function get isMaxSizeSet():Boolean |
| { |
| var explicitMaxW:Number = super.explicitMaxWidth; |
| var explicitMaxH:Number = super.explicitMaxHeight; |
| |
| return (!isNaN(explicitMaxW) && !isNaN(explicitMaxH)); |
| } |
| |
| /** |
| * @private |
| * Return the original height if the soft keyboard is active. This height |
| * is used to stabilize AUTO positioning so that the position is based |
| * on the original height of the Callout instead of a possibly shorter |
| * height due to soft keyboard effects. |
| */ |
| mx_internal function get calloutHeight():Number |
| { |
| return (isSoftKeyboardEffectActive) ? softKeyboardEffectCachedHeight : getLayoutBoundsHeight(); |
| } |
| |
| /** |
| * @private |
| * Compute max width and max height. Uses the the owner and screen bounds |
| * as well as preferred positions to determine max width and max height |
| * for all possible exterior and interior positions. |
| */ |
| mx_internal function commitMaxSize():void |
| { |
| var ownerBounds:Rectangle = owner.getBounds(systemManager.getSandboxRoot()); |
| var ownerLeft:Number = ownerBounds.left; |
| var ownerRight:Number = ownerBounds.right; |
| var ownerTop:Number = ownerBounds.top; |
| var ownerBottom:Number = ownerBounds.bottom; |
| var maxW:Number; |
| var maxH:Number; |
| |
| switch (actualHorizontalPosition) |
| { |
| case CalloutPosition.MIDDLE: |
| { |
| // Callout matches screen width |
| maxW = screen.width - (margin * 2); |
| break; |
| } |
| case CalloutPosition.START: |
| case CalloutPosition.END: |
| { |
| // Flip left and right when using inner positions |
| ownerLeft = ownerBounds.right; |
| ownerRight = ownerBounds.left; |
| |
| // Fall through |
| } |
| default: |
| { |
| // Maximum is the larger of the actual position or flipped position |
| maxW = Math.max(ownerLeft, screen.right - ownerRight) - margin; |
| break; |
| } |
| } |
| |
| // If preferred position was AUTO, then allow maxWidth to grow to |
| // fit the interior position if the owner is wide |
| if ((horizontalPosition == CalloutPosition.AUTO) && |
| (ownerBounds.width > maxW)) |
| maxW += ownerBounds.width; |
| |
| switch (actualVerticalPosition) |
| { |
| case CalloutPosition.MIDDLE: |
| { |
| // Callout matches screen height |
| maxH = screen.height - (margin * 2); |
| break; |
| } |
| case CalloutPosition.START: |
| case CalloutPosition.END: |
| { |
| // Flip top and bottom when using inner positions |
| ownerTop = ownerBounds.bottom; |
| ownerBottom = ownerBounds.top; |
| |
| // Fall through |
| } |
| default: |
| { |
| // Maximum is the larger of the actual position or flipped position |
| maxH = Math.max(ownerTop, screen.bottom - ownerBottom) - margin; |
| break; |
| } |
| } |
| |
| // If preferred position was AUTO, then allow maxHeight to grow to |
| // fit the interior position if the owner is tall |
| if ((verticalPosition == CalloutPosition.AUTO) && |
| (ownerBounds.height > maxH)) |
| maxH += ownerBounds.height; |
| |
| calloutMaxWidth = maxW; |
| calloutMaxHeight = maxH; |
| } |
| |
| /** |
| * @private |
| */ |
| mx_internal function determineArrowPosition(horizontalPos:String, verticalPos:String):String |
| { |
| // Determine arrow direction, outer positions get priority. |
| // Corner positions and center show no arrow |
| var direction:String = ArrowDirection.NONE; |
| |
| if (horizontalPos == CalloutPosition.BEFORE) |
| { |
| if ((verticalPos != CalloutPosition.BEFORE) |
| && (verticalPos != CalloutPosition.AFTER)) |
| { |
| direction = ArrowDirection.RIGHT; |
| } |
| } |
| else if (horizontalPos == CalloutPosition.AFTER) |
| { |
| if ((verticalPos != CalloutPosition.BEFORE) |
| && (verticalPos != CalloutPosition.AFTER)) |
| { |
| direction = ArrowDirection.LEFT; |
| } |
| } |
| else if (verticalPos == CalloutPosition.BEFORE) |
| { |
| direction = ArrowDirection.DOWN; |
| } |
| else if (verticalPos == CalloutPosition.AFTER) |
| { |
| direction = ArrowDirection.UP; |
| } |
| else if (horizontalPos == CalloutPosition.START) |
| { |
| direction = ArrowDirection.LEFT; |
| } |
| else if (horizontalPos == CalloutPosition.END) |
| { |
| direction = ArrowDirection.RIGHT; |
| } |
| else if (verticalPos == CalloutPosition.START) |
| { |
| direction = ArrowDirection.UP; |
| } |
| else if (verticalPos == CalloutPosition.END) |
| { |
| direction = ArrowDirection.DOWN; |
| } |
| |
| return direction; |
| } |
| |
| /** |
| * @private |
| * |
| * Uses horizontalPosition and verticalPosition to determine the bounds of |
| * the callout. |
| */ |
| mx_internal function determinePosition(horizontalPos:String, verticalPos:String, |
| matrix:Matrix, registrationPoint:Point):Rectangle |
| { |
| var ownerVisualElement:ILayoutElement = owner as ILayoutElement; |
| var ownerWidth:Number = (ownerVisualElement) ? ownerVisualElement.getLayoutBoundsWidth() : owner.width; |
| var ownerHeight:Number = (ownerVisualElement) ? ownerVisualElement.getLayoutBoundsHeight() : owner.height; |
| var calloutWidth:Number = getLayoutBoundsWidth(); |
| var calloutHeight:Number = this.calloutHeight; |
| |
| switch (horizontalPos) |
| { |
| case CalloutPosition.BEFORE: |
| { |
| // The full width of the callout is before the owner |
| // All arrow directions are ArrowDirection.RIGHT x=(width - arrow.width) |
| registrationPoint.x = -calloutWidth; |
| break; |
| } |
| case CalloutPosition.START: |
| { |
| // ArrowDirection.LEFT is at x=0 |
| registrationPoint.x = 0; |
| break; |
| } |
| case CalloutPosition.END: |
| { |
| // The ends of the owner and callout are aligned |
| registrationPoint.x = (ownerWidth - calloutWidth); |
| break; |
| } |
| case CalloutPosition.AFTER: |
| { |
| // The full width of the callout is after the owner |
| // All arrow directions are ArrowDirection.LEFT (x=0) |
| registrationPoint.x = ownerWidth; |
| break; |
| } |
| default: // case CalloutPosition.MIDDLE: |
| { |
| registrationPoint.x = Math.floor((ownerWidth - calloutWidth) / 2); |
| break; |
| } |
| } |
| |
| switch (verticalPos) |
| { |
| case CalloutPosition.BEFORE: |
| { |
| // The full height of the callout is before the owner |
| // All arrow directions are ArrowDirection.DOWN y=(height - arrow.height) |
| registrationPoint.y = -calloutHeight; |
| break; |
| } |
| case CalloutPosition.START: |
| { |
| // ArrowDirection.UP is at y=0 |
| registrationPoint.y = 0; |
| break; |
| } |
| case CalloutPosition.MIDDLE: |
| { |
| registrationPoint.y = Math.floor((ownerHeight - calloutHeight) / 2); |
| break; |
| } |
| case CalloutPosition.END: |
| { |
| // The ends of the owner and callout are aligned |
| registrationPoint.y = (ownerHeight - calloutHeight); |
| break; |
| } |
| default: //case CalloutPosition.AFTER: |
| { |
| // The full height of the callout is after the owner |
| // All arrow directions are ArrowDirection.UP (y=0) |
| registrationPoint.y = ownerHeight; |
| break; |
| } |
| } |
| |
| var topLeft:Point = registrationPoint.clone(); |
| var size:Point = MatrixUtil.transformBounds(calloutWidth, calloutHeight, matrix, topLeft); |
| var bounds:Rectangle = new Rectangle(); |
| |
| bounds.left = topLeft.x; |
| bounds.top = topLeft.y; |
| bounds.width = size.x; |
| bounds.height = size.y; |
| |
| return bounds; |
| } |
| |
| /** |
| * @private |
| */ |
| mx_internal function get isArrowVertical():Boolean |
| { |
| return (arrowDirection == ArrowDirection.UP || |
| arrowDirection == ArrowDirection.DOWN); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Event handlers |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| private function arrow_resizeHandler(event:Event):void |
| { |
| updateSkinDisplayList(); |
| } |
| |
| /** |
| * @private |
| */ |
| private function systemManager_resizeHandler(event:Event):void |
| { |
| // Remove explicit settings if due to Resize effect |
| softKeyboardEffectResetExplicitSize(); |
| |
| // Screen resize might require a new arrow direction and callout position |
| invalidatePosition(); |
| |
| if (!isSoftKeyboardEffectActive) |
| { |
| // Force validation and use new screen size only if the keyboard |
| // effect is not active. The stage dimensions may be invalid while |
| // the soft keyboard is active. See SDK-31860. |
| validateNow(); |
| } |
| } |
| } |
| } |