//////////////////////////////////////////////////////////////////////////////// // // 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.layouts { import mx.core.IVisualElement; import spark.components.supportClasses.GroupBase; import spark.core.NavigationUnit; import spark.layouts.supportClasses.LayoutBase; /** * The ViewMenuLayout class defines the layout of the ViewMenu container. * The menu can have multiple rows depending on the number of menu items. * *
The requestedMaxColumnCount
property
* defines the maximum number of menu items in a row.
* By default, the property is set to three.
The ViewMenuLayout class define the layout as follows:
* *requestedMaxColumnCount
property contains
* the default value of three, the menu items are displayed in a single row.
* Each menu item has the same size.
* If you define four or more menu items, meaning more menu items
* than specified by the requestedMaxColumnCount
property,
* the ViewMenu container creates multiple rows.
requestedMaxColumnCount
property,
* each row contains the same number of menu items.
* Each menu item is the same size.
* For example the requestedMaxColumnCount
property
* is set to the default value of three and you define six menu items.
* The menu displays two rows, each containing three menu items.
requestedMaxColumnCount
property,
* rows can contain a different number of menu items.
* The size of the menu items depends on the number of menu items
* in the row.
* For example the requestedMaxColumnCount
property
* is set to the default value of three and you define eight menu items.
* The menu displays three rows.
* The first row contains two menu items.
* The second and third rows each contains three items.
You can create your own custom layout for the menu by creating * your own layout class. * By default, the spark.skins.mobile.ViewMenuSkin class defines * the skin for the ViewMenu container. * To apply a customized ViewMenuLayout class to the ViewMenu container, * define a new skin class for the ViewMenu container.
* *The ViewMenuSkin class includes a definition for a Group
* container named contentGroup
, as shown below:
* <s:Group id="contentGroup" left="0" right="0" top="3" bottom="2" * minWidth="0" minHeight="0"> * <s:layout> * <s:ViewMenuLayout horizontalGap="2" verticalGap="2" id="contentGroupLayout" * requestedMaxColumnCount="3" requestedMaxColumnCount.landscapeGroup="6"/> * </s:layout> * </s:Group>* *
To apply your customized ViewMenuLayout class, your skin class
* should define a container named contentGroup
.
* That container uses the layout
property
* to specify your customized layout class.
The <s:ViewMenuLayout>
tag inherits all of the tag
* attributes of its superclass and adds the following tag attributes:
* <s:ViewMenuLayout * Properties * horizontalGap="2" * requestedMaxColumnCount="3" * verticalGap="2" * /> ** * @see spark.components.ViewMenu * @see spark.components.ViewMenuItem * @see spark.skins.mobile.ViewMenuSkin * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public class ViewMenuLayout extends LayoutBase { /** * Constructor. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function ViewMenuLayout() { super(); } private var numColsInRow:Array; //-------------------------------------------------------------------------- // // Properties // //-------------------------------------------------------------------------- private var _horizontalGap:Number = 2; [Bindable("propertyChange")] [Inspectable(category="General")] /** * The horizontal space between columns, in pixels. * * @see #verticalGap * @default 2 * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get horizontalGap():Number { return _horizontalGap; } /** * @private */ public function set horizontalGap(value:Number):void { if (value == _horizontalGap) return; _horizontalGap = value; invalidateTargetSizeAndDisplayList(); } private var _verticalGap:Number = 2; [Bindable("propertyChange")] [Inspectable(category="General")] /** * The vertical space between rows, in pixels. * * @see #horizontalGap * @default 2 * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get verticalGap():Number { return _verticalGap; } /** * @private */ public function set verticalGap(value:Number):void { if (value == _verticalGap) return; _verticalGap = value; invalidateTargetSizeAndDisplayList(); } private var _requestedMaxColumnCount:int = 3; /** * The maximum number of columns to display in a row. * * @default 3 * * @langversion 3.0 * @playerversion AIR 1.5 * @productversion Flex 4 */ public function get requestedMaxColumnCount():int { return _requestedMaxColumnCount; } /** * @private */ public function set requestedMaxColumnCount(value:int):void { if (_requestedMaxColumnCount == value) return; _requestedMaxColumnCount = value; invalidateTargetSizeAndDisplayList(); } private var rowHeight:Number = 0; //-------------------------------------------------------------------------- // // Overridden Methods // //-------------------------------------------------------------------------- // TODO (jszeto) Fix up logic to use brick algorithm. Might not have full number of columns /** * @private */ override public function measure():void { super.measure(); var layoutTarget:GroupBase = target; if (!layoutTarget) return; var numItems:int = layoutTarget.numElements; var numRows:int = Math.ceil(numItems / requestedMaxColumnCount); var numColumns:int = Math.ceil(numItems / numRows); var maxItemWidth:Number = 0; var maxItemHeight:Number = 0; for (var i:int = 0; i < numItems; i++) { var item:IVisualElement = layoutTarget.getElementAt(i); maxItemWidth = Math.max(maxItemWidth, item.getPreferredBoundsWidth()); maxItemHeight = Math.max(maxItemHeight, item.getPreferredBoundsHeight()); } layoutTarget.measuredWidth = Math.ceil(maxItemWidth * numColumns) + (numColumns - 1) * horizontalGap; layoutTarget.measuredHeight = Math.ceil(maxItemHeight * numRows) + (numRows - 1) * verticalGap; // Save the maxItemHeight and use in updateDisplayList as the height for all items rowHeight = maxItemHeight; } /** * @private */ override public function updateDisplayList(width:Number, height:Number):void { super.updateDisplayList(width, height); var layoutTarget:GroupBase = target; if (!layoutTarget) return; numColsInRow = []; var xPos:Number = 0; var yPos:Number = 0; var itemIndex:int = 0; var itemW:int = 0; var extraWidth:int = 0; var numItems:int = layoutTarget.numElements; var numRows:int = Math.ceil(numItems / requestedMaxColumnCount); var numColumns:int = Math.ceil(numItems / numRows); // Calculate the number of empty spots by getting the inverse of the column mod var emptySpots:int = (numItems % numColumns) > 0 ? numColumns - numItems % numColumns : 0; for (var rowIndex:int = 0; rowIndex < numRows; rowIndex++) { var currentRowColumns:int = (emptySpots > 0) ? numColumns - 1 : numColumns; var viewWidth:Number = width - (currentRowColumns - 1) * horizontalGap; var w:Number = itemW = Math.floor(viewWidth / currentRowColumns); numColsInRow.push(currentRowColumns); // Keep track of the extra pixels since we round off the item widths extraWidth = Math.round(viewWidth - w * currentRowColumns); for (var colIndex:int = 0; colIndex < currentRowColumns; itemIndex++, colIndex++) { var item:IVisualElement = layoutTarget.getElementAt(itemIndex); // Add a pixel of extra width to the first item if (extraWidth > 0) { itemW += 1; extraWidth--; } item.setLayoutBoundsPosition(xPos, yPos); item.setLayoutBoundsSize(itemW, rowHeight); xPos += itemW + horizontalGap; itemW = w; } xPos = 0; yPos += rowHeight + verticalGap; numItems -= currentRowColumns; emptySpots = (numItems % numColumns) > 0 ? numColumns - numItems % numColumns : 0; } } /** * @private */ override public function getNavigationDestinationIndex(currentIndex:int, navigationUnit:uint, arrowKeysWrapFocus:Boolean):int { if (!target || target.numElements < 1) return -1; var maxIndex:int = target.numElements - 1; var newIndex:int = 0; var numRows:int = Math.ceil(target.numElements / requestedMaxColumnCount); if (currentIndex == -1) { if (navigationUnit == NavigationUnit.RIGHT || navigationUnit == NavigationUnit.DOWN) return 0; else return -1; } var currentRow:int = getRowForIndex(currentIndex); var currentColCount:int; var newColCount:int; if (navigationUnit == NavigationUnit.LEFT || navigationUnit == NavigationUnit.RIGHT) { newIndex = currentIndex + (navigationUnit == NavigationUnit.LEFT ? -1 : 1); // We don't support wrapping, so if the old and new index are // on different rows, then don't change the index. if (getRowForIndex(newIndex) != currentRow) newIndex = currentIndex; } else if (navigationUnit == NavigationUnit.UP) { if (currentRow == 0) return currentIndex; currentColCount = numColsInRow[currentRow]; newColCount = numColsInRow[currentRow - 1]; newIndex = currentIndex - newColCount; // If the newIndex isn't on the previous row, then we need to shift // it back one more spot. This situation only occurs when the // number of columns in the two rows are different if ((getRowForIndex(newIndex) != currentRow - 1) && (currentColCount != newColCount)) { newIndex--; } } else if (navigationUnit == NavigationUnit.DOWN) { if (currentRow == numRows - 1) return currentIndex; // Assumes that the smaller column rows are always above the larger column rows newIndex = currentIndex + numColsInRow[currentRow]; } if (newIndex > maxIndex) newIndex = maxIndex; else if (newIndex < 0) newIndex = 0; return newIndex; } // Helper function that figures out the row of a particular index private function getRowForIndex(index:int):int { var currentRow:int = 0; while (currentRow < numColsInRow.length) { index -= numColsInRow[currentRow]; if (index >= 0) currentRow++; else break; } return currentRow; } // Helper function private function invalidateTargetSizeAndDisplayList():void { var g:GroupBase = target; if (!g) return; g.invalidateSize(); g.invalidateDisplayList(); } } }