| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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 mx.controls |
| { |
| |
| import flash.display.DisplayObject; |
| import flash.events.Event; |
| import flash.events.EventPhase; |
| import flash.events.KeyboardEvent; |
| import flash.events.MouseEvent; |
| import flash.ui.Keyboard; |
| |
| import mx.containers.BoxDirection; |
| import mx.controls.buttonBarClasses.ButtonBarButton; |
| import mx.core.ClassFactory; |
| import mx.core.EdgeMetrics; |
| import mx.core.FlexVersion; |
| import mx.core.IFlexDisplayObject; |
| import mx.core.IFlexModuleFactory; |
| import mx.core.IUIComponent; |
| import mx.core.mx_internal; |
| import mx.events.ChildExistenceChangedEvent; |
| import mx.events.ItemClickEvent; |
| import mx.managers.IFocusManagerComponent; |
| import mx.styles.CSSSelector; |
| import mx.styles.CSSStyleDeclaration; |
| import mx.styles.IStyleManager2; |
| |
| use namespace mx_internal; |
| |
| //-------------------------------------- |
| // Events |
| //-------------------------------------- |
| |
| /** |
| * Dispatched when a user clicks a button. |
| * This event is only dispatched if the <code>dataProvider</code> property |
| * does not refer to a ViewStack container. |
| * |
| * @eventType mx.events.ItemClickEvent.ITEM_CLICK |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Event(name="itemClick", type="mx.events.ItemClickEvent")] |
| |
| //-------------------------------------- |
| // Styles |
| //-------------------------------------- |
| |
| /** |
| * Height of each button, in pixels. |
| * If undefined, the height of each button is determined by the font styles |
| * applied to the container. |
| * If you set this property, the specified value overrides this calculation. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="buttonHeight", type="Number", format="Length", inherit="no")] |
| |
| /** |
| * Name of CSS style declaration that specifies styles for the buttons. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="buttonStyleName", type="String", inherit="no")] |
| |
| /** |
| * Width of each button, in pixels. |
| * If undefined, the default width of each button is calculated from its label text. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="buttonWidth", type="Number", format="Length", inherit="no")] |
| |
| /** |
| * Name of CSS style declaration that specifies styles for the first button. |
| * If this is unspecified, the default value |
| * of the <code>buttonStyleName</code> style property is used. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="firstButtonStyleName", type="String", inherit="no")] |
| |
| /** |
| * Horizontal alignment of all buttons within the ButtonBar. Since individual |
| * buttons stretch to fill the entire ButtonBar, this style is only useful if you |
| * use the buttonWidth style and the combined widths of the buttons are less than |
| * than the width of the ButtonBar. |
| * Possible values are <code>"left"</code>, <code>"center"</code>, |
| * and <code>"right"</code>. |
| * |
| * @default "center" |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="horizontalAlign", type="String", enumeration="left,center,right", inherit="no")] |
| |
| /** |
| * Number of pixels between children in the horizontal direction. |
| * |
| * The default value for the Halo theme is <code>0</code>. |
| * The default value for the Spark theme is <code>-1</code>. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="horizontalGap", type="Number", format="Length", inherit="no")] |
| |
| /** |
| * Name of CSS style declaration that specifies styles for the last button. |
| * If this is unspecified, the default value |
| * of the <code>buttonStyleName</code> style property is used. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="lastButtonStyleName", type="String", inherit="no")] |
| |
| /** |
| * Vertical alignment of all buttons within the ButtonBar. Since individual |
| * buttons stretch to fill the entire ButtonBar, this style is only useful if you |
| * use the buttonHeight style and the combined heights of the buttons are less than |
| * than the width of the ButtonBar. |
| * Possible values are <code>"top"</code>, <code>"middle"</code>, |
| * and <code>"bottom"</code>. |
| * |
| * @default "middle" |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="verticalAlign", type="String", enumeration="top,middle,bottom", inherit="no")] |
| |
| /** |
| * Number of pixels between children in the vertical direction. |
| * |
| * @default 0 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="verticalGap", type="Number", format="Length", inherit="no")] |
| |
| //-------------------------------------- |
| // Excluded APIs |
| //-------------------------------------- |
| |
| [Exclude(name="horizontalLineScrollSize", kind="property")] |
| [Exclude(name="horizontalPageScrollSize", kind="property")] |
| [Exclude(name="horizontalScrollBar", kind="property")] |
| [Exclude(name="horizontalScrollPolicy", kind="property")] |
| [Exclude(name="horizontalScrollPosition", kind="property")] |
| [Exclude(name="maxHorizontalScrollPosition", kind="property")] |
| [Exclude(name="maxVerticalScrollPosition", kind="property")] |
| [Exclude(name="verticalLineScrollSize", kind="property")] |
| [Exclude(name="verticalPageScrollSize", kind="property")] |
| [Exclude(name="verticalScrollBar", kind="property")] |
| [Exclude(name="verticalScrollPolicy", kind="property")] |
| [Exclude(name="verticalScrollPosition", kind="property")] |
| |
| [Exclude(name="scroll", kind="event")] |
| [Exclude(name="click", kind="event")] |
| |
| [Exclude(name="backgroundAlpha", kind="style")] |
| [Exclude(name="backgroundAttachment", kind="style")] |
| [Exclude(name="backgroundColor", kind="style")] |
| [Exclude(name="backgroundImage", kind="style")] |
| [Exclude(name="backgroundSize", kind="style")] |
| [Exclude(name="borderColor", kind="style")] |
| [Exclude(name="borderSides", kind="style")] |
| [Exclude(name="borderSkin", kind="style")] |
| [Exclude(name="borderStyle", kind="style")] |
| [Exclude(name="borderThickness", kind="style")] |
| [Exclude(name="cornerRadius", kind="style")] |
| [Exclude(name="dropShadowColor", kind="style")] |
| [Exclude(name="dropShadowEnabled", kind="style")] |
| [Exclude(name="horizontalScrollBarStyleName", kind="style")] |
| [Exclude(name="shadowCapColor", kind="style")] |
| [Exclude(name="shadowColor", kind="style")] |
| [Exclude(name="shadowDirection", kind="style")] |
| [Exclude(name="shadowDistance", kind="style")] |
| [Exclude(name="verticalScrollBarStyleName", kind="style")] |
| |
| //-------------------------------------- |
| // Other metadata |
| //-------------------------------------- |
| |
| [DefaultProperty("dataProvider")] |
| |
| [DefaultTriggerEvent("itemClick")] |
| |
| [IconFile("ButtonBar.png")] |
| |
| [Alternative(replacement="spark.components.ButtonBar", since="4.0")] |
| |
| [MaxChildren(0)] |
| |
| /** |
| * The ButtonBar control defines a horizontal or vertical group of |
| * logically related push buttons with a common look and navigation. |
| * |
| * <p>A push button is one that does not remember its selected state |
| * when selected. |
| * The typical use for a push button in a button bar is for grouping |
| * a set of related buttons together, which gives them a common look |
| * and navigation, and handling the logic for the <code>click</code> event |
| * in a single place. </p> |
| * |
| * <p>The ButtonBar control creates Button controls based on the value of |
| * its <code>dataProvider</code> property. |
| * Even though ButtonBar is a subclass of Container, do not use methods such as |
| * <code>Container.addChild()</code> and <code>Container.removeChild()</code> |
| * to add or remove Button controls. |
| * Instead, use methods such as <code>addItem()</code> and <code>removeItem()</code> |
| * to manipulate the <code>dataProvider</code> property. |
| * The ButtonBar control automatically adds or removes the necessary children based on |
| * changes to the <code>dataProvider</code> property.</p> |
| * |
| * <p>To control the styling of the buttons of the ButtonBar control, use the |
| * <code>buttonStyleName</code>, <code>firstButtonStyleName</code>, |
| * and <code>lastButtonStyleName</code> style properties; |
| * do not try to style the individual Button controls |
| * that make up the ButtonBar control.</p> |
| * |
| * <p>You can use the ToggleButtonBar control to define a group |
| * of toggle buttons.</p> |
| * |
| * <p>ButtonBar control has the following default characteristics:</p> |
| * <table class="innertable"> |
| * <tr> |
| * <th>Characteristic</th> |
| * <th>Description</th> |
| * </tr> |
| * <tr> |
| * <td>Preferred size</td> |
| * <td>Wide enough to contain all buttons with their label text and icons, if any, plus any padding and separators, and high enough to accommodate the button height.</td> |
| * </tr> |
| * <tr> |
| * <td>Control resizing rules</td> |
| * <td>The controls do not resize by default. Specify percentage sizes if you want your ButtonBar to resize based on the size of its parent container.</td> |
| * </tr> |
| * <tr> |
| * <td>Padding</td> |
| * <td>0 pixels for the top, bottom, left, and right properties.</td> |
| * </tr> |
| * </table> |
| * |
| * @mxml |
| * |
| * <p>The <code><mx:ButtonBar></code> tag inherits all the tag attributes |
| * of its superclass, and adds the following tag attributes:</p> |
| * |
| * <pre> |
| * <mx:ButtonBar |
| * <b>Styles</b> |
| * buttonHeight="undefined" |
| * buttonStyleName="<i>Name of CSS style declaration, which specifies |
| * styles for the buttons</i>" |
| * buttonWidth="undefined" |
| * firstButtonStyleName="<i>The value of</i> <code>buttonStyleName</code>" |
| * horizontalAlign="center|left|right" |
| * horizontalGap="0" |
| * lastButtonStyleName="<i>The value of</i> <code>buttonStyleName</code>" |
| * verticalAlign="middle|top|bottom" |
| * verticalGap="0" |
| * |
| * <b>Events</b> |
| * itemClick="<i>No default</i>" |
| * > |
| * ... |
| * <i>child tags</i> |
| * ... |
| * </mx:ButtonBar> |
| * </pre> |
| * |
| * @see mx.controls.ToggleButtonBar |
| * @see mx.controls.LinkBar |
| * @includeExample examples/ButtonBarExample.mxml |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public class ButtonBar extends NavBar implements IFocusManagerComponent |
| { |
| include "../core/Version.as"; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Constructor |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Constructor. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function ButtonBar() |
| { |
| super(); |
| |
| tabEnabled = true; |
| tabFocusEnabled = true; |
| |
| navItemFactory = new ClassFactory(ButtonBarButton); |
| |
| // Add event listeners for scaleX/scaleY changed. |
| // Since we hard-code sizes into our children, |
| // scaling can cause rounding errors so we need |
| // to clear our hard-coded values whenever the scale changes. |
| addEventListener("scaleXChanged", scaleChangedHandler); |
| addEventListener("scaleYChanged", scaleChangedHandler); |
| |
| addEventListener(ChildExistenceChangedEvent.CHILD_REMOVE, |
| childRemoveHandler); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Variables |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Internal flag to indicate when a click event has been triggered |
| * programmatically, as opposed to an actual user click. |
| * This happens when the button selection happens by keyboard navigation |
| * or when selectedIndex is set programmatically. |
| * When this is true, the focus rect shouldn't be drawn |
| * for the currently selected button. |
| */ |
| mx_internal var simulatedClickTriggerEvent:Event = null; |
| |
| /** |
| * @private |
| * Name of style used to specify buttonStyleName. |
| * Overridden by TabBar. |
| */ |
| mx_internal var buttonStyleNameProp:String = "buttonStyleName"; |
| |
| /** |
| * @private |
| * Name of style used to specify buttonStyleName. |
| * Overridden by TabBar. |
| */ |
| mx_internal var firstButtonStyleNameProp:String = "firstButtonStyleName"; |
| |
| /** |
| * @private |
| * Name of style used to specify buttonStyleName. |
| * Overridden by TabBar. |
| */ |
| mx_internal var lastButtonStyleNameProp:String = "lastButtonStyleName"; |
| |
| /** |
| * @private |
| * Name of style used to specify buttonWidth. |
| * Overridden by TabBar. |
| */ |
| mx_internal var buttonWidthProp:String = "buttonWidth"; |
| |
| /** |
| * @private |
| * Name of style used to specify buttonHeight. |
| * Overridden by TabBar. |
| */ |
| mx_internal var buttonHeightProp:String = "buttonHeight"; |
| |
| /** |
| * @private |
| * Flag indicating whether buttons widths should be recalculated. |
| */ |
| private var recalcButtonWidths:Boolean = false; |
| |
| /** |
| * @private |
| * Flag indicating whether buttons heights should be recalculated. |
| */ |
| private var recalcButtonHeights:Boolean = false; |
| |
| /** |
| * @private |
| * The value of the unscaledWidth parameter during the most recent |
| * call to updateDisplayList |
| */ |
| private var oldUnscaledWidth:Number; |
| |
| /** |
| * @private |
| * The value of the unscaledHeight parameter during the most recent |
| * call to updateDisplayList |
| */ |
| private var oldUnscaledHeight:Number; |
| |
| /** |
| * @private |
| * Index of currently focused child. |
| */ |
| mx_internal var focusedIndex:int = 0; |
| |
| /** |
| * @private |
| * Flag indicating whether direction has changed. |
| */ |
| private var directionChanged:Boolean = false; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Overridden properties |
| // |
| //-------------------------------------------------------------------------- |
| |
| //---------------------------------- |
| // borderMetrics |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| override public function get borderMetrics():EdgeMetrics |
| { |
| return EdgeMetrics.EMPTY; |
| } |
| |
| //---------------------------------- |
| // direction |
| //---------------------------------- |
| |
| [Bindable("directionChanged")] |
| [Inspectable(category="General", enumeration="vertical,horizontal", defaultValue="horizontal")] |
| |
| /** |
| * @private |
| */ |
| override public function set direction(value:String):void |
| { |
| if (initialized && value != direction) |
| { |
| directionChanged = true; |
| invalidateProperties(); |
| } |
| |
| super.direction = value; |
| } |
| |
| //---------------------------------- |
| // viewMetrics |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| override public function get viewMetrics():EdgeMetrics |
| { |
| return EdgeMetrics.EMPTY; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Overridden methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| override public function styleChanged(styleProp:String):void |
| { |
| var allStyles:Boolean = styleProp == null || styleProp == "styleName"; |
| |
| super.styleChanged(styleProp); |
| |
| if (allStyles || |
| styleProp == buttonStyleNameProp || |
| styleProp == firstButtonStyleNameProp || |
| styleProp == lastButtonStyleNameProp) |
| { |
| resetNavItems(); |
| } |
| else if (styleProp == buttonWidthProp) |
| { |
| recalcButtonWidths = true; |
| } |
| else if (styleProp == buttonHeightProp) |
| { |
| recalcButtonHeights = true; |
| } |
| else if (styleManager.isInheritingStyle(styleProp) && |
| styleManager.isSizeInvalidatingStyle(styleProp)) |
| { |
| recalcButtonWidths = recalcButtonHeights = true; |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function commitProperties():void |
| { |
| super.commitProperties(); |
| |
| if (directionChanged) |
| { |
| directionChanged = false; |
| |
| // refresh skins |
| var n:int = numChildren; |
| for (var i:int = 0; i < n; i++) |
| Button(getChildAt(i)).changeSkins(); |
| } |
| |
| // Buttons widths and/or heights must be reset before measurement. We do |
| // not reset these flags here since they will be used in updateDisplayList(). |
| if (recalcButtonHeights) |
| resetButtonHeights(); |
| |
| if (recalcButtonWidths) |
| resetButtonWidths(); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function measure():void |
| { |
| super.measure(); |
| |
| var vm:EdgeMetrics = viewMetricsAndPadding; |
| |
| measuredWidth = calcFullWidth() + vm.left + vm.right; |
| measuredHeight = calcFullHeight() + vm.top + vm.bottom; |
| |
| // If explicit button sizes are specified, our preferred sizes are our |
| // minimum sizes. |
| if (getStyle(buttonWidthProp)) |
| measuredMinWidth = measuredWidth; |
| if (getStyle(buttonHeightProp)) |
| measuredMinHeight = measuredHeight; |
| |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function updateDisplayList(unscaledWidth:Number, |
| unscaledHeight:Number):void |
| { |
| // We pre-process our child sizes, so we call super.updateDisplayList() later. |
| |
| const isHorizontal:Boolean = (direction == BoxDirection.HORIZONTAL); |
| const isVertical:Boolean = !isHorizontal; |
| |
| var buttonWidth:Number = getStyle(buttonWidthProp); |
| var buttonHeightStyle:Number = getStyle(buttonHeightProp); |
| var buttonHeight:Number = buttonHeightStyle; |
| |
| var vm:EdgeMetrics = viewMetricsAndPadding; |
| |
| var n:int = numChildren; |
| var horizontalGap:Number = getStyle("horizontalGap"); |
| var verticalGap:Number = getStyle("verticalGap"); |
| var totalHorizontalGap:Number = isHorizontal && numChildren > 0 ? |
| horizontalGap * (n - 1) : 0; |
| var totalVerticalGap:Number = isVertical && numChildren > 0 ? |
| verticalGap * (n - 1) : 0; |
| var w:Number = unscaledWidth - vm.left - vm.right - totalHorizontalGap; |
| var h:Number = unscaledHeight - vm.top - vm.bottom - totalVerticalGap; |
| if (!w || !h) |
| return; |
| |
| if (border) |
| border.visible = false; |
| |
| if (unscaledWidth != oldUnscaledWidth) |
| { |
| recalcButtonWidths = true; |
| oldUnscaledWidth = unscaledWidth; |
| } |
| |
| if (unscaledHeight != oldUnscaledHeight) |
| { |
| recalcButtonHeights = true; |
| oldUnscaledHeight = unscaledHeight; |
| } |
| |
| var i:int; |
| var c:Button; |
| |
| var excessSpace:Number; |
| |
| // See if we need to recalculate the button widths |
| if (recalcButtonWidths) |
| { |
| recalcButtonWidths = false; |
| |
| if (isNaN(buttonWidth) && isVertical) |
| buttonWidth = w; |
| |
| excessSpace = w - (calcFullWidth() - totalHorizontalGap); |
| var averageWidth:int = n > 0 ? w / n : 0; |
| // Number of children larger than average width |
| var nLarge:int = 0; |
| |
| // Sum of all preferred widths (required if excessSpace != 0) |
| var tw:Number = 0; |
| // Sum of all preferred widths of children |
| // smaller than average width. |
| var tSmall:int = 0; |
| if (excessSpace != 0 && isHorizontal) |
| { |
| for (i = 0; i < n; i++) |
| { |
| c = Button(getChildAt(i)); |
| if (isNaN(c.explicitWidth)) |
| { |
| var mw:int = c.measuredWidth; |
| tw += mw; |
| if (mw > averageWidth) |
| nLarge++; |
| else |
| tSmall += mw; |
| } |
| } |
| } |
| else |
| { |
| tw = w; |
| } |
| |
| for (i = 0; i < n; i++) |
| { |
| c = Button(getChildAt(i)); |
| if (isNaN(c.explicitWidth)) |
| { |
| c.minWidth = 0; |
| if (!isNaN(buttonWidth)) |
| { |
| c.minWidth = c.maxWidth = buttonWidth; |
| c.percentWidth = buttonWidth / Math.min(w, tw) * 100; |
| } |
| |
| // Assign measured width to children smaller than average. |
| // Distribute the remaining width to others. |
| else if (excessSpace < 0) |
| { |
| var assignedWidth:Number = c.measuredWidth; |
| if (assignedWidth > averageWidth) |
| assignedWidth = (w - tSmall) / nLarge; |
| |
| c.percentWidth = assignedWidth / w * 100; |
| } |
| |
| // If they fit comfortably with extra space left, |
| // expand them. |
| else if (excessSpace > 0) |
| { |
| c.percentWidth = c.measuredWidth / tw * 100; |
| } |
| |
| else |
| { |
| c.percentWidth = NaN; |
| } |
| |
| // If vertical, expand to fit horizontally. |
| if (isVertical) |
| c.percentWidth = 100; |
| } |
| } |
| } |
| |
| // See if we need to recalculate the button heights |
| if (recalcButtonHeights) |
| { |
| recalcButtonHeights = false; |
| |
| if (isNaN(buttonHeight) && isHorizontal) |
| buttonHeight = h; |
| |
| excessSpace = h - (calcFullHeight() - totalVerticalGap); |
| |
| // Sum of all preferred heights (required if excessSpace != 0). |
| var th:Number = 0; |
| if (excessSpace != 0 && isVertical) |
| { |
| for (i = 0; i < n; i++) |
| { |
| c = Button(getChildAt(i)); |
| if (isNaN(c.explicitHeight)) |
| th += c.measuredHeight; |
| } |
| } |
| |
| for (i = 0; i < n; i++) |
| { |
| c = Button(getChildAt(i)); |
| if (isNaN(c.explicitHeight)) |
| { |
| c.minHeight = 0; |
| if (!isNaN(buttonHeight)) |
| { |
| c.minHeight = buttonHeight; |
| c.percentHeight = buttonHeight / Math.min(th, h) * 100; |
| } |
| if (!isNaN(buttonHeightStyle)) |
| c.maxHeight = buttonHeightStyle; |
| |
| // If horizontal, expand to fit vertically. |
| if (isHorizontal) |
| c.percentHeight = 100; |
| |
| // If they won't fit, squeeze them in. |
| else if (excessSpace < 0) |
| c.percentHeight = c.measuredHeight / th * 100; |
| |
| // If they fit comfortably with extra space left, |
| // expand them. |
| else if (excessSpace > 0) |
| c.percentHeight = c.measuredHeight / th * 100; |
| |
| else |
| c.percentHeight = NaN; |
| } |
| } |
| } |
| |
| // Since we pre-process our child dimensions, we call super.updateDisplayList() |
| // last. |
| super.updateDisplayList(unscaledWidth, unscaledHeight); |
| } |
| |
| /** |
| * @private |
| */ |
| override public function drawFocus(isFocused:Boolean):void |
| { |
| drawButtonFocus(focusedIndex, isFocused); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function createNavItem( |
| label:String, |
| icon:Class = null):IFlexDisplayObject |
| { |
| var newButton:Button = Button(navItemFactory.newInstance()); |
| |
| // Set focusEnabled to false so individual buttons don't get focus. |
| newButton.focusEnabled = false; |
| newButton.label = label; |
| newButton.setStyle("icon", icon); |
| newButton.addEventListener(MouseEvent.CLICK, clickHandler); |
| |
| addChild(newButton); |
| |
| recalcButtonWidths = recalcButtonHeights = true; |
| |
| return newButton; |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function resetNavItems():void |
| { |
| var buttonStyleName:String = getStyle(buttonStyleNameProp); |
| var firstButtonStyleName:String = getStyle(firstButtonStyleNameProp); |
| var lastButtonStyleName:String = getStyle(lastButtonStyleNameProp); |
| |
| if (!buttonStyleName) |
| buttonStyleName = "ButtonBarButton"; |
| if (!firstButtonStyleName) |
| firstButtonStyleName = buttonStyleName; |
| if (!lastButtonStyleName) |
| lastButtonStyleName = buttonStyleName; |
| |
| var button:Button; |
| var n:int = numChildren; |
| for (var i:int = 0; i < n; i++) |
| { |
| button = Button(getChildAt(i)); |
| if (i == 0) |
| { |
| button.styleName = firstButtonStyleName; |
| } |
| else if (i == (n - 1)) |
| { |
| button.styleName = lastButtonStyleName; |
| } |
| else |
| { |
| button.styleName = buttonStyleName; |
| } |
| |
| button.changeSkins(); |
| button.invalidateDisplayList(); |
| } |
| |
| recalcButtonWidths = recalcButtonHeights = true; |
| |
| invalidateDisplayList(); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| private function calcFullWidth():Number |
| { |
| var n:int = numChildren; |
| var gap:Number = 0; |
| |
| if (n == 0) |
| return 0; |
| else if (n > 1) |
| gap = getStyle("horizontalGap"); |
| |
| var horizontal:Boolean = (direction == BoxDirection.HORIZONTAL); |
| |
| var buttonWidth:Number = getStyle(buttonWidthProp); |
| |
| var child:IUIComponent = IUIComponent(getChildAt(0)); |
| |
| var w:Number; |
| if (buttonWidth) |
| w = isNaN(child.explicitWidth) ? buttonWidth : child.explicitWidth; |
| else |
| w = child.getExplicitOrMeasuredWidth(); |
| |
| for (var i:int = 1; i < n; i++) |
| { |
| child = IUIComponent(getChildAt(i)); |
| |
| var cw:Number; |
| if (buttonWidth) |
| { |
| cw = isNaN(child.explicitWidth) ? |
| buttonWidth : |
| child.explicitWidth; |
| } |
| else |
| { |
| cw = child.getExplicitOrMeasuredWidth(); |
| } |
| |
| if (horizontal) |
| w += (gap + cw); |
| else |
| w = Math.max(w, cw); |
| } |
| |
| return w; |
| } |
| |
| /** |
| * @private |
| */ |
| private function calcFullHeight():Number |
| { |
| var n:int = numChildren; |
| var gap:Number; |
| |
| if (n == 0) |
| return 0; |
| else if (n > 1) |
| gap = getStyle("verticalGap"); |
| |
| var vertical:Boolean = (direction == BoxDirection.VERTICAL); |
| |
| var buttonHeight:Number = getStyle(buttonHeightProp); |
| |
| var child:IUIComponent = IUIComponent(getChildAt(0)); |
| |
| var h:Number; |
| if (buttonHeight) |
| { |
| h = isNaN(child.explicitHeight) ? |
| buttonHeight : |
| child.explicitHeight; |
| } |
| else |
| { |
| h = child.getExplicitOrMeasuredHeight(); |
| } |
| |
| for (var i:int = 1; i < n; i++) |
| { |
| child = IUIComponent(getChildAt(i)); |
| |
| var ch:Number; |
| if (buttonHeight) |
| { |
| ch = isNaN(child.explicitHeight) ? |
| buttonHeight :child.explicitHeight; |
| } |
| else |
| { |
| ch = child.getExplicitOrMeasuredHeight(); |
| } |
| |
| if (vertical) |
| h += (gap + ch); |
| else |
| h = Math.max(h, ch); |
| } |
| |
| return h; |
| } |
| |
| /** |
| * @private |
| * Returns the previous valid child index, or -1 if there are no children. |
| * Used by keyboard navigation. |
| */ |
| mx_internal function prevIndex(index:int):int |
| { |
| var n:int = numChildren; |
| return index == 0 ? n - 1 : index - 1; |
| } |
| |
| /** |
| * @private |
| * Returns the next valid child index, or -1 if there are no children. |
| * Used by keyboard navigation. |
| */ |
| mx_internal function nextIndex(index:int):int |
| { |
| var n:int = numChildren; |
| if (n == 0) |
| return -1; |
| else |
| return index == n - 1 ? 0 : index + 1; |
| } |
| |
| /** |
| * @private |
| */ |
| mx_internal function drawButtonFocus(index:int, focused:Boolean):void |
| { |
| if (numChildren > 0 && index < numChildren) |
| { |
| var b:Button = Button(getChildAt(index)); |
| b.drawFocus(focused && focusManager.showFocusIndicator); |
| |
| // internal event for accessibility |
| if (focused) |
| dispatchEvent(new Event("focusDraw")); |
| |
| // If the button is losing focus, set its phase to UP, |
| // which will cause a redraw. |
| if (!focused && b.phase != ButtonPhase.UP) |
| b.phase = ButtonPhase.UP; |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Overridden event handlers: UIComponent |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| override protected function keyDownHandler(event:KeyboardEvent):void |
| { |
| // Ignore events that bubble up from the child ButtonBarButtons. |
| // such as the one we redispatch below from the focused child |
| // when the SPACE key is released. |
| if (event.eventPhase != EventPhase.AT_TARGET) |
| return; |
| |
| // If rtl layout, need to swap LEFT and RIGHT so correct action |
| // is done. |
| var keyCode:uint = mapKeycodeForLayoutDirection(event, true); |
| |
| switch (keyCode) |
| { |
| case Keyboard.DOWN: |
| case Keyboard.RIGHT: |
| { |
| focusManager.showFocusIndicator = true; |
| drawButtonFocus(focusedIndex, false); |
| focusedIndex = nextIndex(focusedIndex); |
| |
| if (focusedIndex != -1) |
| { |
| drawButtonFocus(focusedIndex, true); |
| } |
| |
| event.stopPropagation(); |
| break; |
| } |
| |
| case Keyboard.UP: |
| case Keyboard.LEFT: |
| { |
| focusManager.showFocusIndicator = true; |
| drawButtonFocus(focusedIndex, false); |
| focusedIndex = prevIndex(focusedIndex); |
| |
| if (focusedIndex != -1) |
| { |
| drawButtonFocus(focusedIndex, true); |
| } |
| |
| event.stopPropagation(); |
| break; |
| } |
| |
| case Keyboard.SPACE: |
| { |
| if (focusedIndex != -1) |
| { |
| // Redispatch from the focused ButtonBarButton |
| // to get it to appear pressed. |
| var child:Button = Button(getChildAt(focusedIndex)); |
| child.dispatchEvent(event); |
| } |
| |
| event.stopPropagation(); |
| break; |
| } |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function keyUpHandler(event:KeyboardEvent):void |
| { |
| // Ignore events that bubble up from the child ButtonBarButtons. |
| // such as the one we redispatch below from the focused child |
| // when the SPACE key is released. |
| if (event.eventPhase != EventPhase.AT_TARGET) |
| return; |
| |
| switch (event.keyCode) |
| { |
| case Keyboard.SPACE: |
| { |
| if (focusedIndex != -1) |
| { |
| // Redispatch from the focused ButtonBarButton |
| // to get it to appear released. |
| var child:Button = Button(getChildAt(focusedIndex)); |
| child.dispatchEvent(event); |
| } |
| |
| event.stopPropagation(); |
| break; |
| } |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Overridden event handlers: NavBar |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| override protected function clickHandler(event:MouseEvent):void |
| { |
| if (simulatedClickTriggerEvent == null) |
| { |
| focusedIndex = getChildIndex(DisplayObject(event.currentTarget)); |
| drawButtonFocus(focusedIndex, true); |
| } |
| |
| super.clickHandler(event); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Event handlers |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| private function childRemoveHandler(event:ChildExistenceChangedEvent):void |
| { |
| var child:DisplayObject = event.relatedObject; |
| var index:int = getChildIndex(child); |
| var n:int = numChildren; |
| if (n < 2) |
| { |
| // Don't bother if it's the last child. |
| return; |
| } |
| |
| var buttonStyleName:String = getStyle(buttonStyleNameProp); |
| var firstButtonStyleName:String = getStyle(firstButtonStyleNameProp); |
| var lastButtonStyleName:String = getStyle(lastButtonStyleNameProp); |
| |
| if (!buttonStyleName) |
| buttonStyleName = "buttonBarButtonStyle"; |
| if (!firstButtonStyleName) |
| firstButtonStyleName = buttonStyleName; |
| if (!lastButtonStyleName) |
| lastButtonStyleName = buttonStyleName; |
| |
| // Refresh the skins for the last button that was in this position. |
| if (index == 0 || index == n - 1) |
| { |
| var button:Button = Button(getChildAt(index == n - 1 ? n - 2 : 0)); |
| |
| button.styleName = index == 0 ? |
| firstButtonStyleName : |
| lastButtonStyleName; |
| |
| button.changeSkins(); |
| button.invalidateDisplayList(); |
| } |
| } |
| |
| /** |
| * @private |
| * Reset buttons widths so that it can be recalculated. |
| */ |
| protected function resetButtonWidths():void |
| { |
| for (var i:int = 0; i < numChildren; i++) |
| { |
| var child:Button = getChildAt(i) as Button; |
| if (child) |
| { |
| child.explicitWidth = NaN; |
| child.minWidth = NaN; |
| child.maxWidth = NaN; |
| } |
| } |
| } |
| |
| /** |
| * @private |
| * Reset buttons heights so that it can be recalculated. |
| */ |
| protected function resetButtonHeights():void |
| { |
| for (var i:int = 0; i < numChildren; i++) |
| { |
| var child:Button = getChildAt(i) as Button; |
| if (child) |
| { |
| child.explicitHeight = NaN; |
| child.minHeight = NaN; |
| child.maxHeight = NaN; |
| } |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| private function scaleChangedHandler(event:Event):void |
| { |
| // This is called whenever scaleX or scaleY is changed. |
| // We need to clear out the preferredWidth/preferredHeight |
| // of our children since scaling can cause rounding errors |
| // which then are not corrected when un-scaled. |
| resetButtonHeights(); |
| resetButtonWidths(); |
| } |
| } |
| |
| } |