//////////////////////////////////////////////////////////////////////////////// // // 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:

* * * *

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.

* * @mxml *

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(); } } }