1//////////////////////////////////////////////////////////////////////////////// 2// 3// ADOBE SYSTEMS INCORPORATED 4// Copyright 2008 Adobe Systems Incorporated 5// All Rights Reserved. 6// 7// NOTICE: Adobe permits you to use, modify, and distribute this file 8// in accordance with the terms of the license agreement accompanying it. 9// 10//////////////////////////////////////////////////////////////////////////////// 11 12package spark.layouts 13{ 14import flash.geom.Point; 15import flash.geom.Rectangle; 16 17import mx.core.ILayoutElement; 18import mx.core.IVisualElement; 19import mx.core.mx_internal; 20import mx.events.PropertyChangeEvent; 21 22import spark.components.supportClasses.GroupBase; 23import spark.core.NavigationUnit; 24import spark.layouts.supportClasses.DropLocation; 25import spark.layouts.supportClasses.LayoutBase; 26 27use namespace mx_internal; 28 29/** 30 * The TileLayout class arranges layout elements in columns and rows 31 * of equally-sized cells. 32 * The TileLayout class uses a number of properties that control orientation, 33 * count, size, gap and justification of the columns and the rows 34 * as well as element alignment within the cells. 35 * 36 * <p>Per-element supported constraints are 37 * <code>percentWidth</code> and <code>percentHeight</code>. 38 * Element's minimum and maximum sizes are always be respected and 39 * where possible, an element's size is limited to less then or equal 40 * of the cell size.</p> 41 * 42 * <p>When not explicitly set, the <code>columnWidth</code> property 43 * is calculated as the maximum preferred bounds width of all elements 44 * and the <code>columnHeight</code> property is calculated 45 * as the maximum preferred bounds height of all elements.</p> 46 * 47 * <p>When not explicitly set, the <code>columnCount</code> and 48 * <code>rowCount</code> properties are calculated from 49 * any explicit width and height settings for the layout target, 50 * and <code>columnWidth</code> and <code>columnHeight</code>. 51 * In case none is specified, the <code>columnCount</code> and <code>rowCount</code> 52 * values are picked so that the resulting pixel area is as square as possible.</p> 53 * 54 * <p> The measured size is calculated from the <code>columnCount</code>, <code>rowCount</code>, 55 * <code>columnWidth</code>, <code>rowHeight</code> properties and the gap sizes.</p> 56 * 57 * <p>The default measured size, when no properties were explicitly set, is 58 * as square as possible area and is large enough to fit all elements.</p> 59 * 60 * <p>In other cases the measured size may not be big enough to fit all elements. 61 * For example, when both <code>columnCount</code> and <code>rowCount</code> are explicitly set to values 62 * such that <code>columnCount</code> * <code>rowCount</code> < element count.</p> 63 * 64 * <p>The minimum measured size is calculated the same way as the measured size but 65 * it's guaranteed to encompass enough rows and columns along the minor axis to fit 66 * all elements.</p> 67 * 68 * @mxml 69 * <p>The <code><s:TileLayout></code> tag inherits all of the tag 70 * attributes of its superclass and adds the following tag attributes:</p> 71 * 72 * <pre> 73 * <s:TileLayout 74 * <strong>Properties</strong> 75 * columnAlign="left" 76 * columnWidth="NaN" 77 * horizontalAlign="justify" 78 * horizontalGap="6" 79 * orientation="rows" 80 * requestedColumnCount="-1" 81 * requestedRowCount="-1" 82 * rowAlign="top" 83 * rowCount="-1" 84 * rowHeight="NaN" 85 * verticalAlign="justify" 86 * verticalGap="6" 87 * /> 88 * </pre> 89 * 90 * @langversion 3.0 91 * @playerversion Flash 10 92 * @playerversion AIR 1.5 93 * @productversion Flex 4 94 */ 95public class TileLayout extends LayoutBase 96{ 97 include "../core/Version.as"; 98 99 //-------------------------------------------------------------------------- 100 // 101 // Constructor 102 // 103 //-------------------------------------------------------------------------- 104 105 /** 106 * Constructor. 107 * 108 * @langversion 3.0 109 * @playerversion Flash 10 110 * @playerversion AIR 1.5 111 * @productversion Flex 4 112 */ 113 public function TileLayout():void 114 { 115 super(); 116 } 117 118 //-------------------------------------------------------------------------- 119 // 120 // Properties 121 // 122 //-------------------------------------------------------------------------- 123 124 //---------------------------------- 125 // horizontalGap 126 //---------------------------------- 127 128 private var explicitHorizontalGap:Number = 6; 129 private var _horizontalGap:Number = 6; 130 131 [Bindable("propertyChange")] 132 [Inspectable(category="General")] 133 134 /** 135 * Horizontal space between columns, in pixels. 136 * 137 * @see #verticalGap 138 * @see #columnAlign 139 * @default 6 140 * 141 * @langversion 3.0 142 * @playerversion Flash 10 143 * @playerversion AIR 1.5 144 * @productversion Flex 4 145 */ 146 public function get horizontalGap():Number 147 { 148 return _horizontalGap; 149 } 150 151 /** 152 * @private 153 */ 154 public function set horizontalGap(value:Number):void 155 { 156 explicitHorizontalGap = value; 157 if (value == _horizontalGap) 158 return; 159 160 _horizontalGap = value; 161 invalidateTargetSizeAndDisplayList(); 162 } 163 164 //---------------------------------- 165 // verticalGap 166 //---------------------------------- 167 168 private var explicitVerticalGap:Number = 6; 169 private var _verticalGap:Number = 6; 170 171 [Bindable("propertyChange")] 172 [Inspectable(category="General")] 173 174 /** 175 * Vertical space between rows, in pixels. 176 * 177 * @see #horizontalGap 178 * @see #rowAlign 179 * @default 6 180 * 181 * @langversion 3.0 182 * @playerversion Flash 10 183 * @playerversion AIR 1.5 184 * @productversion Flex 4 185 */ 186 public function get verticalGap():Number 187 { 188 return _verticalGap; 189 } 190 191 /** 192 * @private 193 */ 194 public function set verticalGap(value:Number):void 195 { 196 explicitVerticalGap = value; 197 if (value == _verticalGap) 198 return; 199 200 _verticalGap = value; 201 invalidateTargetSizeAndDisplayList(); 202 } 203 204 //---------------------------------- 205 // columnCount 206 //---------------------------------- 207 208 private var _columnCount:int = -1; 209 210 [Bindable("propertyChange")] 211 [Inspectable(category="General")] 212 213 /** 214 * Contain the actual column count. 215 * 216 * @see #rowCount 217 * @see #columnAlign 218 * @default -1 219 * 220 * @langversion 3.0 221 * @playerversion Flash 10 222 * @playerversion AIR 1.5 223 * @productversion Flex 4 224 */ 225 public function get columnCount():int 226 { 227 return _columnCount; 228 } 229 230 //---------------------------------- 231 // requestedColumnCount 232 //---------------------------------- 233 234 /** 235 * @private 236 * Storage for the requestedColumnCount property. 237 */ 238 private var _requestedColumnCount:int = -1; 239 240 [Inspectable(category="General", minValue="-1")] 241 242 /** 243 * Number of columns to be displayed. 244 * 245 * <p>Set to -1 to allow the TileLayout to determine 246 * the column count automatically.</p> 247 * 248 * <p>If the <code>orientation</code> property is set to <code>TileOrientation.ROWS</code>, 249 * then setting this property has no effect 250 * In this case, the <code>rowCount</code> is explicitly set, and the 251 * container width is explicitly set. </p> 252 * 253 * @see #columnCount 254 * @see #columnAlign 255 * @default -1 256 * 257 * @langversion 3.0 258 * @playerversion Flash 10 259 * @playerversion AIR 1.5 260 * @productversion Flex 4 261 */ 262 public function get requestedColumnCount():int 263 { 264 return _requestedColumnCount; 265 } 266 267 /** 268 * @private 269 */ 270 public function set requestedColumnCount(value:int):void 271 { 272 if (_requestedColumnCount == value) 273 return; 274 275 _requestedColumnCount = value; 276 _columnCount = value; 277 invalidateTargetSizeAndDisplayList(); 278 } 279 280 //---------------------------------- 281 // rowCount 282 //---------------------------------- 283 284 /** 285 * @private 286 * Storage for the rowCount property. 287 */ 288 private var _rowCount:int = -1; 289 290 [Bindable("propertyChange")] 291 [Inspectable(category="General")] 292 293 /** 294 * The row count. 295 * 296 * @see #requestedRowCount 297 * @see #columnCount 298 * @default -1 299 * 300 * @langversion 3.0 301 * @playerversion Flash 10 302 * @playerversion AIR 1.5 303 * @productversion Flex 4 304 */ 305 public function get rowCount():int 306 { 307 return _rowCount; 308 } 309 310 //---------------------------------- 311 // requestedRowCount 312 //---------------------------------- 313 314 /** 315 * @private 316 * Storage for the requestedRowCount property. 317 */ 318 private var _requestedRowCount:int = -1; 319 320 [Inspectable(category="General", minValue="-1")] 321 322 /** 323 * Number of rows to be displayed. 324 * 325 * <p>Set to -1 to remove explicit override and allow the TileLayout to determine 326 * the row count automatically.</p> 327 * 328 * <p>If the <code>orientation</code> property is set to 329 * <code>TileOrientation.COLUMNS</code>, setting this property has no effect. 330 * in this case, <code>columnCount</code> is explicitly set, and the 331 * container height is explicitly set.</p> 332 * 333 * @see #rowCount 334 * @see #rowAlign 335 * @default -1 336 * 337 * @langversion 3.0 338 * @playerversion Flash 10 339 * @playerversion AIR 1.5 340 * @productversion Flex 4 341 */ 342 public function get requestedRowCount():int 343 { 344 return _requestedRowCount; 345 } 346 347 /** 348 * @private 349 */ 350 public function set requestedRowCount(value:int):void 351 { 352 if (_requestedRowCount == value) 353 return; 354 355 _requestedRowCount = value; 356 _rowCount = value; 357 invalidateTargetSizeAndDisplayList(); 358 } 359 360 //---------------------------------- 361 // columnWidth 362 //---------------------------------- 363 364 private var explicitColumnWidth:Number = NaN; 365 private var _columnWidth:Number = NaN; 366 367 [Bindable("propertyChange")] 368 [Inspectable(category="General", minValue="0.0")] 369 370 /** 371 * Contain the actual column width, in pixels. 372 * 373 * <p>If not explicitly set, the column width is 374 * determined from the width of the widest element. </p> 375 * 376 * <p>If the <code>columnAlign</code> property is set 377 * to <code>"justifyUsingWidth"</code>, the column width grows to the 378 * container width to justify the fully-visible columns.</p> 379 * 380 * @see #rowHeight 381 * @see #columnAlign 382 * @default NaN 383 * 384 * @langversion 3.0 385 * @playerversion Flash 10 386 * @playerversion AIR 1.5 387 * @productversion Flex 4 388 */ 389 public function get columnWidth():Number 390 { 391 return _columnWidth; 392 } 393 394 /** 395 * @private 396 */ 397 public function set columnWidth(value:Number):void 398 { 399 explicitColumnWidth = value; 400 if (value == _columnWidth) 401 return; 402 403 _columnWidth = value; 404 invalidateTargetSizeAndDisplayList(); 405 } 406 407 //---------------------------------- 408 // rowHeight 409 //---------------------------------- 410 411 private var explicitRowHeight:Number = NaN; 412 private var _rowHeight:Number = NaN; 413 414 [Bindable("propertyChange")] 415 [Inspectable(category="General", minValue="0.0")] 416 417 /** 418 * The row height, in pixels. 419 * 420 * <p>If not explicitly set, the row height is 421 * determined from the maximum of elements' height.</p> 422 * 423 * If <code>rowAlign</code> is set to "justifyUsingHeight", the actual row height 424 * increases to justify the fully-visible rows to the container height. 425 * 426 * @see #columnWidth 427 * @see #rowAlign 428 * @default NaN 429 * 430 * @langversion 3.0 431 * @playerversion Flash 10 432 * @playerversion AIR 1.5 433 * @productversion Flex 4 434 */ 435 public function get rowHeight():Number 436 { 437 return _rowHeight; 438 } 439 440 /** 441 * @private 442 */ 443 public function set rowHeight(value:Number):void 444 { 445 explicitRowHeight = value; 446 if (value == _rowHeight) 447 return; 448 449 _rowHeight = value; 450 invalidateTargetSizeAndDisplayList(); 451 } 452 453 //---------------------------------- 454 // paddingLeft 455 //---------------------------------- 456 457 private var _paddingLeft:Number = 0; 458 459 [Inspectable(category="General")] 460 461 /** 462 * The minimum number of pixels between the container's left edge and 463 * the left edge of the layout element. 464 * 465 * @default 0 466 * 467 * @langversion 3.0 468 * @playerversion Flash 10 469 * @playerversion AIR 1.5 470 * @productversion Flex 4 471 */ 472 public function get paddingLeft():Number 473 { 474 return _paddingLeft; 475 } 476 477 /** 478 * @private 479 */ 480 public function set paddingLeft(value:Number):void 481 { 482 if (_paddingLeft == value) 483 return; 484 485 _paddingLeft = value; 486 invalidateTargetSizeAndDisplayList(); 487 } 488 489 //---------------------------------- 490 // paddingRight 491 //---------------------------------- 492 493 private var _paddingRight:Number = 0; 494 495 [Inspectable(category="General")] 496 497 /** 498 * The minimum number of pixels between the container's right edge and 499 * the right edge of the layout element. 500 * 501 * @default 0 502 * 503 * @langversion 3.0 504 * @playerversion Flash 10 505 * @playerversion AIR 1.5 506 * @productversion Flex 4 507 */ 508 public function get paddingRight():Number 509 { 510 return _paddingRight; 511 } 512 513 /** 514 * @private 515 */ 516 public function set paddingRight(value:Number):void 517 { 518 if (_paddingRight == value) 519 return; 520 521 _paddingRight = value; 522 invalidateTargetSizeAndDisplayList(); 523 } 524 525 //---------------------------------- 526 // paddingTop 527 //---------------------------------- 528 529 private var _paddingTop:Number = 0; 530 531 [Inspectable(category="General")] 532 533 /** 534 * Number of pixels between the container's top edge 535 * and the top edge of the first layout element. 536 * 537 * @default 0 538 * 539 * @langversion 3.0 540 * @playerversion Flash 10 541 * @playerversion AIR 1.5 542 * @productversion Flex 4 543 */ 544 public function get paddingTop():Number 545 { 546 return _paddingTop; 547 } 548 549 /** 550 * @private 551 */ 552 public function set paddingTop(value:Number):void 553 { 554 if (_paddingTop == value) 555 return; 556 557 _paddingTop = value; 558 invalidateTargetSizeAndDisplayList(); 559 } 560 561 //---------------------------------- 562 // paddingBottom 563 //---------------------------------- 564 565 private var _paddingBottom:Number = 0; 566 567 [Inspectable(category="General")] 568 569 /** 570 * Number of pixels between the container's bottom edge 571 * and the bottom edge of the last layout element. 572 * 573 * @default 0 574 * 575 * @langversion 3.0 576 * @playerversion Flash 10 577 * @playerversion AIR 1.5 578 * @productversion Flex 4 579 */ 580 public function get paddingBottom():Number 581 { 582 return _paddingBottom; 583 } 584 585 /** 586 * @private 587 */ 588 public function set paddingBottom(value:Number):void 589 { 590 if (_paddingBottom == value) 591 return; 592 593 _paddingBottom = value; 594 invalidateTargetSizeAndDisplayList(); 595 } 596 597 //---------------------------------- 598 // horizontalAlign 599 //---------------------------------- 600 601 private var _horizontalAlign:String = HorizontalAlign.JUSTIFY; 602 603 [Inspectable(category="General", enumeration="left,right,center,justify", defaultValue="justify")] 604 605 /** 606 * Specifies how to align the elements within the cells in the horizontal direction. 607 * Supported values are 608 * <code>HorizontalAlign.LEFT</code>, 609 * <code>HorizontalAlign.CENTER</code>, 610 * <code>HorizontalAlign.RIGHT</code>, 611 * <code>HorizontalAlign.JUSTIFY</code>. 612 * 613 * <p>When set to <code>HorizontalAlign.JUSTIFY</code> the width of each 614 * element is set to the <code>columnWidth</code>.</p> 615 * 616 * @default <code>HorizontalAlign.JUSTIFY</code> 617 * 618 * @langversion 3.0 619 * @playerversion Flash 10 620 * @playerversion AIR 1.5 621 * @productversion Flex 4 622 */ 623 public function get horizontalAlign():String 624 { 625 return _horizontalAlign; 626 } 627 628 /** 629 * @private 630 */ 631 public function set horizontalAlign(value:String):void 632 { 633 if (_horizontalAlign == value) 634 return; 635 636 _horizontalAlign = value; 637 invalidateTargetSizeAndDisplayList(); 638 } 639 640 //---------------------------------- 641 // verticalAlign 642 //---------------------------------- 643 644 private var _verticalAlign:String = VerticalAlign.JUSTIFY; 645 646 [Inspectable(category="General", enumeration="top,bottom,middle,justify", defaultValue="justify")] 647 648 /** 649 * Specifies how to align the elements within the cells in the vertical direction. 650 * Supported values are 651 * <code>VerticalAlign.TOP</code>, 652 * <code>VerticalAlign.MIDDLE</code>, 653 * <code>VerticalAlign.BOTTOM</code>, 654 * <code>VerticalAlign.JUSTIFY</code>. 655 * 656 * <p>When set to <code>VerticalAlign.JUSTIFY</code>, the height of each 657 * element is set to <code>rowHeight</code>.</p> 658 * 659 * @default <code>VerticalAlign.JUSTIFY</code> 660 * 661 * @langversion 3.0 662 * @playerversion Flash 10 663 * @playerversion AIR 1.5 664 * @productversion Flex 4 665 */ 666 public function get verticalAlign():String 667 { 668 return _verticalAlign; 669 } 670 671 /** 672 * @private 673 */ 674 public function set verticalAlign(value:String):void 675 { 676 if (_verticalAlign == value) 677 return; 678 679 _verticalAlign = value; 680 invalidateTargetSizeAndDisplayList(); 681 } 682 683 //---------------------------------- 684 // columnAlign 685 //---------------------------------- 686 687 private var _columnAlign:String = ColumnAlign.LEFT; 688 689 [Inspectable(category="General", enumeration="left,justifyUsingGap,justifyUsingWidth", defaultValue="left")] 690 691 /** 692 * Specifies how to justify the fully visible columns to the container width. 693 * ActionScript values can be <code>ColumnAlign.LEFT</code>, <code>ColumnAlign.JUSTIFY_USING_GAP</code> 694 * and <code>ColumnAlign.JUSTIFY_USING_WIDTH</code>. 695 * MXML values can be <code>"left"</code>, <code>"justifyUsingGap"</code> and <code>"justifyUsingWidth"</code>. 696 * 697 * <p>When set to <code>ColumnAlign.LEFT</code> it turns column justification off. 698 * There may be partially visible columns or whitespace between the last column and 699 * the right edge of the container. This is the default value.</p> 700 * 701 * <p>When set to <code>ColumnAlign.JUSTIFY_USING_GAP</code> the <code>horizontalGap</code> 702 * actual value increases so that 703 * the last fully visible column right edge aligns with the container's right edge. 704 * In case there is only a single fully visible column, the <code>horizontalGap</code> actual value 705 * increases so that it pushes any partially visible column beyond the right edge 706 * of the container. 707 * Note that explicitly setting the <code>horizontalGap</code> property does not turn off 708 * justification. It only determines the initial gap value. 709 * Justification may increases it.</p> 710 * 711 * <p>When set to <code>ColumnAlign.JUSTIFY_USING_WIDTH</code> the <code>columnWidth</code> 712 * actual value increases so that 713 * the last fully visible column right edge aligns with the container's right edge. 714 * Note that explicitly setting the <code>columnWidth</code> property does not turn off justification. 715 * It only determines the initial column width value. 716 * Justification may increases it.</p> 717 * 718 * @see #horizontalGap 719 * @see #columnWidth 720 * @see #rowAlign 721 * @default ColumnAlign.LEFT 722 * 723 * @langversion 3.0 724 * @playerversion Flash 10 725 * @playerversion AIR 1.5 726 * @productversion Flex 4 727 */ 728 public function get columnAlign():String 729 { 730 return _columnAlign; 731 } 732 733 /** 734 * @private 735 */ 736 public function set columnAlign(value:String):void 737 { 738 if (_columnAlign == value) 739 return; 740 741 _columnAlign = value; 742 invalidateTargetSizeAndDisplayList(); 743 } 744 745 //---------------------------------- 746 // rowAlign 747 //---------------------------------- 748 749 private var _rowAlign:String = RowAlign.TOP; 750 751 [Inspectable(category="General", enumeration="top,justifyUsingGap,justifyUsingHeight", defaultValue="top")] 752 753 /** 754 * Specifies how to justify the fully visible rows to the container height. 755 * ActionScript values can be <code>RowAlign.TOP</code>, <code>RowAlign.JUSTIFY_USING_GAP</code> 756 * and <code>RowAlign.JUSTIFY_USING_HEIGHT</code>. 757 * MXML values can be <code>"top"</code>, <code>"justifyUsingGap"</code> and <code>"justifyUsingHeight"</code>. 758 * 759 * <p>When set to <code>RowAlign.TOP</code> it turns column justification off. 760 * There might be partially visible rows or whitespace between the last row and 761 * the bottom edge of the container. This is the default value.</p> 762 * 763 * <p>When set to <code>RowAlign.JUSTIFY_USING_GAP</code> the <code>verticalGap</code> 764 * actual value increases so that 765 * the last fully visible row bottom edge aligns with the container's bottom edge. 766 * In case there is only a single fully visible row, the value of <code>verticalGap</code> 767 * increases so that it pushes any partially visible row beyond the bottom edge 768 * of the container. Note that explicitly setting the <code>verticalGap</code> does not turn off 769 * justification, but just determines the initial gap value. 770 * Justification can then increases it.</p> 771 * 772 * <p>When set to <code>RowAlign.JUSTIFY_USING_HEIGHT</code> the <code>rowHeight</code> 773 * actual value increases so that 774 * the last fully visible row bottom edge aligns with the container's bottom edge. Note that 775 * explicitly setting the <code>rowHeight</code> does not turn off justification, but 776 * determines the initial row height value. 777 * Justification can then increase it.</p> 778 * 779 * @see #verticalGap 780 * @see #rowHeight 781 * @see #columnAlign 782 * @default RowAlign.TOP 783 * 784 * @langversion 3.0 785 * @playerversion Flash 10 786 * @playerversion AIR 1.5 787 * @productversion Flex 4 788 */ 789 public function get rowAlign():String 790 { 791 return _rowAlign; 792 } 793 794 /** 795 * @private 796 */ 797 public function set rowAlign(value:String):void 798 { 799 if (_rowAlign == value) 800 return; 801 802 _rowAlign = value; 803 invalidateTargetSizeAndDisplayList(); 804 } 805 806 //---------------------------------- 807 // orientation 808 //---------------------------------- 809 810 private var _orientation:String = TileOrientation.ROWS; 811 812 [Inspectable(category="General", enumeration="rows,columns", defaultValue="rows")] 813 814 /** 815 * Specifies whether elements are arranged row by row or 816 * column by column. 817 * ActionScript values can be <code>TileOrientation.ROWS</code> and 818 * <code>TileOrientation.COLUMNS</code>. 819 * MXML values can be <code>"rows"</code> and <code>"columns"</code>. 820 * 821 * @default TileOrientation.ROWS 822 * 823 * @langversion 3.0 824 * @playerversion Flash 10 825 * @playerversion AIR 1.5 826 * @productversion Flex 4 827 */ 828 public function get orientation():String 829 { 830 return _orientation; 831 } 832 833 /** 834 * @private 835 */ 836 public function set orientation(value:String):void 837 { 838 if (_orientation == value) 839 return; 840 841 _orientation = value; 842 _tileWidthCached = _tileHeightCached = NaN; 843 invalidateTargetSizeAndDisplayList(); 844 } 845 846 //---------------------------------- 847 // useVirtualLayout 848 //---------------------------------- 849 850 /** 851 * @private 852 */ 853 override public function set useVirtualLayout(value:Boolean):void 854 { 855 if (useVirtualLayout == value) 856 return; 857 858 super.useVirtualLayout = value; 859 860 // Reset the state that virtual depends on. If the layout has already 861 // run with useVirtualLayout=false, the visibleStartEndIndex variables 862 // will have been set to 0, dataProvider.length. 863 if (value) 864 { 865 visibleStartIndex = -1; 866 visibleEndIndex = -1; 867 visibleStartX = 0; 868 visibleStartY = 0; 869 } 870 } 871 872 //-------------------------------------------------------------------------- 873 // 874 // Variables 875 // 876 //-------------------------------------------------------------------------- 877 878 /** 879 * @private 880 */ 881 override public function clearVirtualLayoutCache():void 882 { 883 _tileWidthCached = _tileHeightCached = NaN; 884 } 885 886 /** 887 * @private 888 * storage for old property values, in order to dispatch change events. 889 */ 890 private var oldColumnWidth:Number = NaN; 891 private var oldRowHeight:Number = NaN; 892 private var oldColumnCount:int = -1; 893 private var oldRowCount:int = -1; 894 private var oldHorizontalGap:Number = NaN; 895 private var oldVerticalGap:Number = NaN; 896 897 // Cache storage to avoid repeating work from measure() in updateDisplayList(). 898 // These are set the first time the value is calculated and are reset at the end 899 // of updateDisplayList(). 900 private var _tileWidthCached:Number = NaN; 901 private var _tileHeightCached:Number = NaN; 902 private var _numElementsCached:int = -1; 903 904 /** 905 * @private 906 * The following variables are used by updateDisplayList() and set by 907 * calculateDisplayParameters(). If virtualLayout=true they're based 908 * on the current scrollRect. 909 */ 910 private var visibleStartIndex:int = -1; // dataProvider/layout element index 911 private var visibleEndIndex:int = -1; // ... 912 private var visibleStartX:Number = 0; // first tile/cell origin 913 private var visibleStartY:Number = 0; // ... 914 915 //-------------------------------------------------------------------------- 916 // 917 // Class methods 918 // 919 //-------------------------------------------------------------------------- 920 921 /** 922 * Dispatches events if Actual values have changed since the last call. 923 * Checks columnWidth, rowHeight, columnCount, rowCount, horizontalGap, verticalGap. 924 * This method is called from within updateDisplayList() 925 * 926 * @langversion 3.0 927 * @playerversion Flash 10 928 * @playerversion AIR 1.5 929 * @productversion Flex 4 930 */ 931 private function dispatchEventsForActualValueChanges():void 932 { 933 if (hasEventListener(PropertyChangeEvent.PROPERTY_CHANGE)) 934 { 935 if (oldColumnWidth != _columnWidth) 936 dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "columnWidth", oldColumnWidth, _columnWidth)); 937 if (oldRowHeight != _rowHeight) 938 dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "rowHeight", oldRowHeight, _rowHeight)); 939 if (oldColumnCount != _columnCount) 940 dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "columnCount", oldColumnCount, _columnCount)); 941 if (oldRowCount != _rowCount) 942 dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "rowCount", oldRowCount, _rowCount)); 943 if (oldHorizontalGap != _horizontalGap) 944 dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "horizontalGap", oldHorizontalGap, _horizontalGap)); 945 if (oldVerticalGap != _verticalGap) 946 dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "verticalGap", oldVerticalGap, _verticalGap)); 947 } 948 949 oldColumnWidth = _columnWidth; 950 oldRowHeight = _rowHeight; 951 oldColumnCount = _columnCount; 952 oldRowCount = _rowCount; 953 oldHorizontalGap = _horizontalGap; 954 oldVerticalGap = _verticalGap; 955 } 956 957 /** 958 * This method is called from measure() and updateDisplayList() to calculate the 959 * actual values for columnWidth, rowHeight, columnCount, rowCount, horizontalGap and verticalGap. 960 * The width and height should include padding because the padding is accounted for in 961 * the calculations. 962 * 963 * @param width - the width during measure() is the layout target explicitWidth or NaN 964 * and during updateDisplayList() is the unscaledWidth. 965 * @param height - the height during measure() is the layout target explicitHeight or NaN 966 * and during updateDisplayList() is the unscaledHeight. 967 * 968 * @langversion 3.0 969 * @playerversion Flash 10 970 * @playerversion AIR 1.5 971 * @productversion Flex 4 972 */ 973 private function updateActualValues(width:Number, height:Number):void 974 { 975 var widthMinusPadding:Number = width - paddingLeft - paddingRight; 976 var heightMinusPadding:Number = height - paddingTop - paddingBottom; 977 978 // First, figure the tile size 979 calculateTileSize(); 980 981 // Second, figure out number of rows/columns 982 var elementCount:int = calculateElementCount(); 983 calculateColumnAndRowCount(widthMinusPadding, heightMinusPadding, elementCount); 984 985 // Third, adjust the gaps and column and row sizes based on justification settings 986 _horizontalGap = explicitHorizontalGap; 987 _verticalGap = explicitVerticalGap; 988 989 // Justify 990 if (!isNaN(width)) 991 { 992 switch (columnAlign) 993 { 994 case ColumnAlign.JUSTIFY_USING_GAP: 995 _horizontalGap = justifyByGapSize(widthMinusPadding, _columnWidth, _horizontalGap, _columnCount); 996 break; 997 case ColumnAlign.JUSTIFY_USING_WIDTH: 998 _columnWidth = justifyByElementSize(widthMinusPadding, _columnWidth, _horizontalGap, _columnCount); 999 break; 1000 } 1001 } 1002 1003 if (!isNaN(height)) 1004 { 1005 switch (rowAlign) 1006 { 1007 case RowAlign.JUSTIFY_USING_GAP: 1008 _verticalGap = justifyByGapSize(heightMinusPadding, _rowHeight, _verticalGap, _rowCount); 1009 break; 1010 case RowAlign.JUSTIFY_USING_HEIGHT: 1011 _rowHeight = justifyByElementSize(heightMinusPadding, _rowHeight, _verticalGap, _rowCount); 1012 break; 1013 } 1014 } 1015 1016 // Last, if we have explicit overrides for both rowCount and columnCount, then 1017 // make sure that column/row count along the minor axis reflects the actual count. 1018 if (-1 != _requestedColumnCount && -1 != _requestedRowCount) 1019 { 1020 if (orientation == TileOrientation.ROWS) 1021 _rowCount = Math.max(1, Math.ceil(elementCount / Math.max(1, _requestedColumnCount))); 1022 else 1023 _columnCount = Math.max(1, Math.ceil(elementCount / Math.max(1, _requestedRowCount))); 1024 } 1025 } 1026 1027 /** 1028 * @private 1029 * Returns true, if the dimensions (colCount1, rowCount1) are more square than (colCount2, rowCount2). 1030 * Squareness is the difference between width and height of a tile layout 1031 * with the specified number of columns and rows. 1032 */ 1033 private function closerToSquare(colCount1:int, rowCount1:int, colCount2:int, rowCount2:int):Boolean 1034 { 1035 var difference1:Number = Math.abs(colCount1 * (_columnWidth + _horizontalGap) - _horizontalGap - 1036 rowCount1 * (_rowHeight + _verticalGap) + _verticalGap); 1037 var difference2:Number = Math.abs(colCount2 * (_columnWidth + _horizontalGap) - _horizontalGap - 1038 rowCount2 * (_rowHeight + _verticalGap) + _verticalGap); 1039 1040 return difference1 < difference2 || (difference1 == difference2 && rowCount1 <= rowCount2); 1041 } 1042 1043 /** 1044 * @private 1045 * Calculates _columnCount and _rowCount based on width, height, 1046 * orientation, _requestedColumnCount, _requestedRowCount, _columnWidth, _rowHeight. 1047 * _columnWidth and _rowHeight must be valid before calling. 1048 * 1049 * The width and height should not include padding. 1050 */ 1051 private function calculateColumnAndRowCount(width:Number, height:Number, elementCount:int):void 1052 { 1053 _columnCount = _rowCount = -1; 1054 1055 if (-1 != _requestedColumnCount || -1 != _requestedRowCount) 1056 { 1057 if (-1 != _requestedRowCount) 1058 _rowCount = Math.max(1, _requestedRowCount); 1059 1060 if (-1 != _requestedColumnCount) 1061 _columnCount = Math.max(1, _requestedColumnCount); 1062 } 1063 // Figure out number of columns or rows based on the explicit size along one of the axes 1064 else if (!isNaN(width) && (orientation == TileOrientation.ROWS || isNaN(height))) 1065 { 1066 if (_columnWidth + explicitHorizontalGap > 0) 1067 _columnCount = Math.max(1, Math.floor((width + explicitHorizontalGap) / (_columnWidth + explicitHorizontalGap))); 1068 else 1069 _columnCount = 1; 1070 } 1071 else if (!isNaN(height) && (orientation == TileOrientation.COLUMNS || isNaN(width))) 1072 { 1073 if (_rowHeight + explicitVerticalGap > 0) 1074 _rowCount = Math.max(1, Math.floor((height + explicitVerticalGap) / (_rowHeight + explicitVerticalGap))); 1075 else 1076 _rowCount = 1; 1077 } 1078 else // Figure out the number of columns and rows so that pixels area occupied is as square as possible 1079 { 1080 // Calculate number of rows and columns so that 1081 // pixel area is as square as possible 1082 var hGap:Number = explicitHorizontalGap; 1083 var vGap:Number = explicitVerticalGap; 1084 1085 // 1. columnCount * (columnWidth + hGap) - hGap == rowCount * (rowHeight + vGap) - vGap 1086 // 1. columnCount * (columnWidth + hGap) == rowCount * (rowHeight + vGap) + hGap - vGap 1087 // 1. columnCount == (rowCount * (rowHeight + vGap) + hGap - vGap) / (columnWidth + hGap) 1088 // 2. columnCount * rowCount == elementCount 1089 // substitute 1. in 2. 1090 // rowCount * rowCount + (hGap - vGap) * rowCount - elementCount * (columnWidth + hGap ) == 0 1091 1092 var a:Number = Math.max(0, (rowHeight + vGap)); 1093 var b:Number = (hGap - vGap); 1094 var c:Number = -elementCount * (_columnWidth + hGap); 1095 var d:Number = b * b - 4 * a * c; // Always guaranteed to be greater than zero, since c <= 0 1096 d = Math.sqrt(d); 1097 1098 // We are guaranteed that we have only one positive root, since d >= b: 1099 var rowCount:Number = (a != 0) ? (b + d) / (2 * a) : elementCount; 1100 1101 // To get integer count for the columns/rows we round up and down so 1102 // we get four possible solutions. Then we pick the best one. 1103 var row1:int = Math.max(1, Math.floor(rowCount)); 1104 var col1:int = Math.max(1, Math.ceil(elementCount / row1)); 1105 row1 = Math.max(1, Math.ceil(elementCount / col1)); 1106 1107 var row2:int = Math.max(1, Math.ceil(rowCount)); 1108 var col2:int = Math.max(1, Math.ceil(elementCount / row2)); 1109 row2 = Math.max(1, Math.ceil(elementCount / col2)); 1110 1111 var col3:int = Math.max(1, Math.floor(elementCount / rowCount)); 1112 var row3:int = Math.max(1, Math.ceil(elementCount / col3)); 1113 col3 = Math.max(1, Math.ceil(elementCount / row3)); 1114 1115 var col4:int = Math.max(1, Math.ceil(elementCount / rowCount)); 1116 var row4:int = Math.max(1, Math.ceil(elementCount / col4)); 1117 col4 = Math.max(1, Math.ceil(elementCount / row4)); 1118 1119 if (closerToSquare(col3, row3, col1, row1)) 1120 { 1121 col1 = col3; 1122 row1 = row3; 1123 } 1124 1125 if (closerToSquare(col4, row4, col2, row2)) 1126 { 1127 col2 = col4; 1128 row2 = row4; 1129 } 1130 1131 if (closerToSquare(col1, row1, col2, row2)) 1132 { 1133 _columnCount = col1; 1134 _rowCount = row1; 1135 } 1136 else 1137 { 1138 _columnCount = col2; 1139 _rowCount = row2; 1140 } 1141 } 1142 1143 // In case we determined only columns or rows (from explicit overrides or explicit width/height) 1144 // calculate the other from the number of elements 1145 if (-1 == _rowCount) 1146 _rowCount = Math.max(1, Math.ceil(elementCount / _columnCount)); 1147 if (-1 == _columnCount) 1148 _columnCount = Math.max(1, Math.ceil(elementCount / _rowCount)); 1149 } 1150 1151 /** 1152 * @private 1153 * Increases the gap so that elements are justified to exactly fit totalSize 1154 * leaving no partially visible elements in view. 1155 * @return Returs the new gap size. 1156 */ 1157 private function justifyByGapSize(totalSize:Number, elementSize:Number, 1158 gap:Number, elementCount:int):Number 1159 { 1160 // If element + gap collapses to zero, then don't adjust the gap. 1161 if (elementSize + gap <= 0) 1162 return gap; 1163 1164 // Find the number of fully visible elements 1165 var visibleCount:int = 1166 Math.min(elementCount, Math.floor((totalSize + gap) / (elementSize + gap))); 1167 1168 // If there isn't even a singel fully visible element, don't adjust the gap 1169 if (visibleCount < 1) 1170 return gap; 1171 1172 // Special case: if there's a singe fully visible element and a partially 1173 // visible element, then make the gap big enough to push out the partially 1174 // visible element out of view. 1175 if (visibleCount == 1) 1176 return elementCount > 1 ? Math.max(gap, totalSize - elementSize) : gap; 1177 1178 // Now calculate the gap such that the fully visible elements and gaps 1179 // add up exactly to totalSize: 1180 // <==> totalSize == visibleCount * elementSize + (visibleCount - 1) * gap 1181 // <==> totalSize - visibleCount * elementSize == (visibleCount - 1) * gap 1182 // <==> (totalSize - visibleCount * elementSize) / (visibleCount - 1) == gap 1183 return (totalSize - visibleCount * elementSize) / (visibleCount - 1); 1184 } 1185 1186 /** 1187 * @private 1188 * Increases the element size so that elements are justified to exactly fit 1189 * totalSize leaving no partially visible elements in view. 1190 * @return Returns the the new element size. 1191 */ 1192 private function justifyByElementSize(totalSize:Number, elementSize:Number, 1193 gap:Number, elementCount:int):Number 1194 { 1195 var elementAndGapSize:Number = elementSize + gap; 1196 var visibleCount:int = 0; 1197 // Find the number of fully visible elements 1198 if (elementAndGapSize == 0) 1199 visibleCount = elementCount; 1200 else 1201 visibleCount = Math.min(elementCount, Math.floor((totalSize + gap) / elementAndGapSize)); 1202 1203 // If there isn't event a single fully visible element, don't adjust 1204 if (visibleCount < 1) 1205 return elementSize; 1206 1207 // Now calculate the elementSize such that the fully visible elements and gaps 1208 // add up exactly to totalSize: 1209 // <==> totalSize == visibleCount * elementSize + (visibleCount - 1) * gap 1210 // <==> totalSize - (visibleCount - 1) * gap == visibleCount * elementSize 1211 // <==> (totalSize - (visibleCount - 1) * gap) / visibleCount == elementSize 1212 return (totalSize - (visibleCount - 1) * gap) / visibleCount; 1213 } 1214 1215 /** 1216 * @private 1217 * Update _tileWidth,Height to be the maximum of their current 1218 * value and the element's preferred bounds. 1219 */ 1220 private function updateVirtualTileSize(elt:ILayoutElement):void 1221 { 1222 if (!elt || !elt.includeInLayout) 1223 return; 1224 var w:Number = elt.getPreferredBoundsWidth(); 1225 var h:Number = elt.getPreferredBoundsHeight(); 1226 _tileWidthCached = isNaN(_tileWidthCached) ? w : Math.max(w, _tileWidthCached); 1227 _tileHeightCached = isNaN(_tileHeightCached) ? h : Math.max(h, _tileHeightCached); 1228 } 1229 1230 /** 1231 * @private 1232 */ 1233 private function calculateVirtualTileSize():void 1234 { 1235 // If both dimensions are explicitly set, we're done 1236 _columnWidth = explicitColumnWidth; 1237 _rowHeight = explicitRowHeight; 1238 if (!isNaN(_columnWidth) && !isNaN(_rowHeight)) 1239 { 1240 _tileWidthCached = _columnWidth; 1241 _tileHeightCached = _rowHeight; 1242 return; 1243 } 1244 1245 // update _tileWidth,HeightCached based on the typicalElement 1246 updateVirtualTileSize(typicalLayoutElement); 1247 1248 // update _tileWidth,HeightCached based on visible elements 1249 if ((visibleStartIndex != -1) && (visibleEndIndex != -1)) 1250 { 1251 for (var index:int = visibleStartIndex; index <= visibleEndIndex; index++) 1252 updateVirtualTileSize(target.getVirtualElementAt(index)); 1253 } 1254 1255 // Make sure that we always have non-NaN values in the cache, even 1256 // when there are no elements. 1257 if (isNaN(_tileWidthCached)) 1258 _tileWidthCached = 0; 1259 if (isNaN(_tileHeightCached)) 1260 _tileHeightCached = 0; 1261 1262 if (isNaN(_columnWidth)) 1263 _columnWidth = _tileWidthCached; 1264 if (isNaN(_rowHeight)) 1265 _rowHeight = _tileHeightCached; 1266 } 1267 1268 /** 1269 * @private 1270 * Calculates _columnWidth and _rowHeight from maximum of 1271 * elements preferred size and any explicit overrides. 1272 */ 1273 private function calculateRealTileSize():void 1274 { 1275 _columnWidth = _tileWidthCached; 1276 _rowHeight = _tileHeightCached; 1277 if (!isNaN(_columnWidth) && !isNaN(_rowHeight)) 1278 return; 1279 1280 // Are both dimensions explicitly set? 1281 _columnWidth = _tileWidthCached = explicitColumnWidth; 1282 _rowHeight = _tileHeightCached = explicitRowHeight; 1283 if (!isNaN(_columnWidth) && !isNaN(_rowHeight)) 1284 return; 1285 1286 // Find the maxmimums of element's preferred sizes 1287 var columnWidth:Number = 0; 1288 var rowHeight:Number = 0; 1289 1290 var layoutTarget:GroupBase = target; 1291 var count:int = layoutTarget.numElements; 1292 // Remember the number of includeInLayout elements 1293 _numElementsCached = count; 1294 for (var i:int = 0; i < count; i++) 1295 { 1296 var el:ILayoutElement = layoutTarget.getElementAt(i); 1297 if (!el || !el.includeInLayout) 1298 { 1299 _numElementsCached--; 1300 continue; 1301 } 1302 1303 if (isNaN(_columnWidth)) 1304 columnWidth = Math.max(columnWidth, el.getPreferredBoundsWidth()); 1305 if (isNaN(_rowHeight)) 1306 rowHeight = Math.max(rowHeight, el.getPreferredBoundsHeight()); 1307 } 1308 1309 if (isNaN(_columnWidth)) 1310 _columnWidth = _tileWidthCached = columnWidth; 1311 if (isNaN(_rowHeight)) 1312 _rowHeight = _tileHeightCached = rowHeight; 1313 } 1314 1315 private function calculateTileSize():void 1316 { 1317 if (useVirtualLayout) 1318 calculateVirtualTileSize(); 1319 else 1320 calculateRealTileSize(); 1321 } 1322 1323 /** 1324 * @private 1325 * For normal layout return the number of non-null includeInLayout=true 1326 * layout elements, for virtual layout just return the number of layout 1327 * elements. 1328 */ 1329 private function calculateElementCount():int 1330 { 1331 if (-1 != _numElementsCached) 1332 return _numElementsCached; 1333 1334 var layoutTarget:GroupBase = target; 1335 var count:int = layoutTarget.numElements; 1336 _numElementsCached = count; 1337 1338 if (useVirtualLayout) 1339 return _numElementsCached; 1340 1341 for (var i:int = 0; i < count; i++) 1342 { 1343 var el:ILayoutElement = layoutTarget.getElementAt(i); 1344 if (!el || !el.includeInLayout) 1345 _numElementsCached--; 1346 } 1347 1348 return _numElementsCached; 1349 } 1350 1351 /** 1352 * @private 1353 * This method computes values for visibleStartX,Y, visibleStartIndex, and 1354 * visibleEndIndex based on the TileLayout geometry values, like _columnWidth 1355 * and _rowHeight, computed by calculateActualValues(). 1356 * 1357 * If useVirtualLayout=false, then visibleStartX,Y=0 and visibleStartIndex=0 1358 * and visibleEndIndex=layoutTarget.numElements-1. 1359 * 1360 * If useVirtualLayout=true and orientation=ROWS then visibleStartIndex is the 1361 * layout element index of the item at first visible row relative to the scrollRect, 1362 * column 0. Note that we're using column=0 instead of the first visible column 1363 * to simplify the iteration logic in updateDisplayList(). This is optimal 1364 * for the common case where the entire row is visible. Optimally handling 1365 * the case where orientation=ROWS and each row is only partially visible is 1366 * doable but adds some complexity to the main loop. 1367 * 1368 * The logic for useVirtualLayout=true and orientation=COLS is similar. 1369 */ 1370 private function calculateDisplayParameters(unscaledWidth:int, unscaledHeight:int):void 1371 { 1372 updateActualValues(unscaledWidth, unscaledHeight); 1373 1374 var layoutTarget:GroupBase = target; 1375 var eltCount:int = layoutTarget.numElements; 1376 visibleStartX = paddingLeft; // initial values for xPos,yPos in updateDisplayList 1377 visibleStartY = paddingTop; 1378 visibleStartIndex = 0; 1379 visibleEndIndex = eltCount - 1; 1380 1381 if (useVirtualLayout) 1382 { 1383 var hsp:Number = layoutTarget.horizontalScrollPosition - paddingLeft; 1384 var vsp:Number = layoutTarget.verticalScrollPosition - paddingTop; 1385 var cwg:Number = _columnWidth + _horizontalGap; 1386 var rwg:Number = _rowHeight + _verticalGap; 1387 1388 var visibleCol0:int = Math.max(0, Math.floor(hsp / cwg)); 1389 var visibleRow0:int = Math.max(0, Math.floor(vsp / rwg)); 1390 var visibleCol1:int = Math.min(_columnCount - 1, Math.floor((hsp + unscaledWidth) / cwg)); 1391 var visibleRow1:int = Math.min(_rowCount - 1, Math.floor((vsp + unscaledHeight) / rwg)); 1392 1393 if (orientation == TileOrientation.ROWS) 1394 { 1395 visibleStartIndex = (visibleRow0 * _columnCount); 1396 visibleEndIndex = Math.min(eltCount - 1, (visibleRow1 * _columnCount) + visibleCol1); 1397 visibleStartY = visibleRow0 * rwg + paddingTop; 1398 } 1399 else 1400 { 1401 visibleStartIndex = (visibleCol0 * _rowCount); 1402 visibleEndIndex = Math.min(eltCount - 1, (visibleCol1 * _rowCount) + visibleRow1); 1403 visibleStartX = visibleCol0 * cwg + paddingLeft; 1404 } 1405 } 1406 } 1407 1408 /** 1409 * @private 1410 * This method is called by updateDisplayList() after initial values for 1411 * visibleStartIndex, visibleEndIndex have been calculated. We 1412 * re-calculateDisplayParameters() to account for the possibility that 1413 * larger cells may have been exposed. Since tileWidth,Height can only 1414 * increase, the new visibleStart,EndIndex values will be greater than or 1415 * equal to the old ones. 1416 */ 1417 private function updateVirtualLayout(unscaledWidth:int, unscaledHeight:int):void 1418 { 1419 var oldVisibleStartIndex:int = visibleStartIndex; 1420 var oldVisibleEndIndex:int = visibleEndIndex; 1421 calculateDisplayParameters(unscaledWidth, unscaledHeight); // compute new visibleStart,EndIndex values 1422 1423 // We're responsible for laying out *all* of the elements requested 1424 // with getVirtualElementAt(), even if they don't fall within the final 1425 // visible range. Hide any extra ones. On the next layout pass, they'll 1426 // be added to DataGroup::freeRenderers 1427 1428 var layoutTarget:GroupBase = target; 1429 for (var i:int = oldVisibleStartIndex; i <= oldVisibleEndIndex; i++) 1430 { 1431 if ((i >= visibleStartIndex) && (i <= visibleEndIndex)) // skip past the visible range 1432 { 1433 i = visibleEndIndex; 1434 continue; 1435 } 1436 var el:ILayoutElement = layoutTarget.getElementAt(i); 1437 if (!el) 1438 continue; 1439 if (el is IVisualElement) 1440 IVisualElement(el).visible = false; 1441 } 1442 } 1443 1444 /** 1445 * Sets the size and the position of the specified layout element and cell bounds. 1446 * @param element - the element to resize and position. 1447 * @param cellX - the x coordinate of the cell. 1448 * @param cellY - the y coordinate of the cell. 1449 * @param cellWidth - the width of the cell. 1450 * @param cellHeight - the height of the cell. 1451 * 1452 * @langversion 3.0 1453 * @playerversion Flash 10 1454 * @playerversion AIR 1.5 1455 * @productversion Flex 4 1456 */ 1457 private function sizeAndPositionElement(element:ILayoutElement, 1458 cellX:int, 1459 cellY:int, 1460 cellWidth:int, 1461 cellHeight:int):void 1462 { 1463 var childWidth:Number = NaN; 1464 var childHeight:Number = NaN; 1465 1466 // Determine size of the element 1467 if (horizontalAlign == "justify") 1468 childWidth = cellWidth; 1469 else if (!isNaN(element.percentWidth)) 1470 childWidth = Math.round(cellWidth * element.percentWidth * 0.01); 1471 else 1472 childWidth = element.getPreferredBoundsWidth(); 1473 1474 if (verticalAlign == "justify") 1475 childHeight = cellHeight; 1476 else if (!isNaN(element.percentHeight)) 1477 childHeight = Math.round(cellHeight * element.percentHeight * 0.01); 1478 else 1479 childHeight = element.getPreferredBoundsHeight(); 1480 1481 // Enforce min and max limits 1482 var maxChildWidth:Number = Math.min(element.getMaxBoundsWidth(), cellWidth); 1483 var maxChildHeight:Number = Math.min(element.getMaxBoundsHeight(), cellHeight); 1484 // Make sure we enforce element's minimum last, since it has the highest priority 1485 childWidth = Math.max(element.getMinBoundsWidth(), Math.min(maxChildWidth, childWidth)); 1486 childHeight = Math.max(element.getMinBoundsHeight(), Math.min(maxChildHeight, childHeight)); 1487 1488 // Size the element 1489 element.setLayoutBoundsSize(childWidth, childHeight); 1490 1491 var x:Number = cellX; 1492 switch (horizontalAlign) 1493 { 1494 case "right": 1495 x += cellWidth - element.getLayoutBoundsWidth(); 1496 break; 1497 case "center": 1498 // Make sure division result is integer - Math.floor() the result. 1499 x = cellX + Math.floor((cellWidth - element.getLayoutBoundsWidth()) / 2); 1500 break; 1501 } 1502 1503 var y:Number = cellY; 1504 switch (verticalAlign) 1505 { 1506 case "bottom": 1507 y += cellHeight - element.getLayoutBoundsHeight(); 1508 break; 1509 case "middle": 1510 // Make sure division result is integer - Math.floor() the result. 1511 y += Math.floor((cellHeight - element.getLayoutBoundsHeight()) / 2); 1512 break; 1513 } 1514 1515 // Position the element 1516 element.setLayoutBoundsPosition(x, y); 1517 } 1518 1519 /** 1520 * @private 1521 * @return Returns the x coordinate of the left edge for the specified column. 1522 */ 1523 final private function leftEdge(columnIndex:int):Number 1524 { 1525 if (columnIndex < 0) 1526 return 0; 1527 1528 return Math.max(0, columnIndex * (_columnWidth + _horizontalGap)) + paddingLeft; 1529 } 1530 1531 /** 1532 * @private 1533 * @return Returns the x coordinate of the right edge for the specified column. 1534 */ 1535 final private function rightEdge(columnIndex:int):Number 1536 { 1537 if (columnIndex < 0) 1538 return 0; 1539 1540 return Math.min(target.contentWidth, columnIndex * (_columnWidth + _horizontalGap) + _columnWidth) + paddingLeft; 1541 } 1542 1543 /** 1544 * @private 1545 * @return Returns the y coordinate of the top edge for the specified row. 1546 */ 1547 final private function topEdge(rowIndex:int):Number 1548 { 1549 if (rowIndex < 0) 1550 return 0; 1551 1552 return Math.max(0, rowIndex * (_rowHeight + _verticalGap)) + paddingTop; 1553 } 1554 1555 /** 1556 * @private 1557 * @return Returns the y coordinate of the bottom edge for the specified row. 1558 */ 1559 final private function bottomEdge(rowIndex:int):Number 1560 { 1561 if (rowIndex < 0) 1562 return 0; 1563 1564 return Math.min(target.contentHeight, rowIndex * (_rowHeight + _verticalGap) + _rowHeight) + paddingTop; 1565 } 1566 1567 /** 1568 * @private 1569 * Convenience function for subclasses that invalidates the 1570 * target's size and displayList so that both layout's <code>measure()</code> 1571 * and <code>updateDisplayList</code> methods get called. 1572 * 1573 * <p>Typically a layout invalidates the target's size and display list so that 1574 * it gets a chance to recalculate the target's default size and also size and 1575 * position the target's elements. For example changing the <code>gap</code> 1576 * property on a <code>VerticalLayout</code> will internally call this method 1577 * to ensure that the elements are re-arranged with the new setting and the 1578 * target's default size is recomputed.</p> 1579 */ 1580 private function invalidateTargetSizeAndDisplayList():void 1581 { 1582 var g:GroupBase = target; 1583 if (!g) 1584 return; 1585 1586 g.invalidateSize(); 1587 g.invalidateDisplayList(); 1588 } 1589 1590 //-------------------------------------------------------------------------- 1591 // 1592 // Overridden methods from LayoutBase 1593 // 1594 //-------------------------------------------------------------------------- 1595 1596 /** 1597 * @private 1598 */ 1599 override protected function scrollPositionChanged():void 1600 { 1601 super.scrollPositionChanged(); 1602 1603 var layoutTarget:GroupBase = target; 1604 if (!layoutTarget) 1605 return; 1606 1607 if (useVirtualLayout) 1608 layoutTarget.invalidateDisplayList(); 1609 } 1610 1611 /** 1612 * @private 1613 */ 1614 override public function measure():void 1615 { 1616 // Save and restore these values so they're not modified 1617 // as a sideeffect of measure(). 1618 var savedColumnCount:int = _columnCount; 1619 var savedRowCount:int = _rowCount; 1620 var savedHorizontalGap:int = _horizontalGap; 1621 var savedVerticalGap:int = _verticalGap; 1622 var savedColumnWidth:int = _columnWidth; 1623 var savedRowHeight:int = _rowHeight; 1624 1625 var layoutTarget:GroupBase = target; 1626 if (!layoutTarget) 1627 return; 1628 1629 updateActualValues(layoutTarget.explicitWidth, layoutTarget.explicitHeight); 1630 1631 // For measure, any explicit overrides for rowCount and columnCount take precedence 1632 var columnCount:int = _requestedColumnCount != -1 ? Math.max(1, _requestedColumnCount) : _columnCount; 1633 var rowCount:int = _requestedRowCount != -1 ? Math.max(1, _requestedRowCount) : _rowCount; 1634 1635 var measuredWidth:Number = 0; 1636 var measuredMinWidth:Number = 0; 1637 var measuredHeight:Number = 0; 1638 var measuredMinHeight:Number = 0; 1639 1640 if (columnCount > 0) 1641 { 1642 measuredWidth = Math.ceil(columnCount * (_columnWidth + _horizontalGap) - _horizontalGap) 1643 // measured min size is guaranteed to have enough columns to fit all elements 1644 measuredMinWidth = Math.ceil(_columnCount * (_columnWidth + _horizontalGap) - _horizontalGap); 1645 } 1646 1647 if (rowCount > 0) 1648 { 1649 measuredHeight = Math.ceil(rowCount * (_rowHeight + _verticalGap) - _verticalGap); 1650 // measured min size is guaranteed to have enough rows to fit all elements 1651 measuredMinHeight = Math.ceil(_rowCount * (_rowHeight + _verticalGap) - _verticalGap); 1652 } 1653 _numElementsCached = -1; 1654 1655 var hPadding:Number = paddingLeft + paddingRight; 1656 var vPadding:Number = paddingTop + paddingBottom; 1657 1658 layoutTarget.measuredWidth = measuredWidth + hPadding; 1659 layoutTarget.measuredMinWidth = measuredMinWidth + hPadding; 1660 layoutTarget.measuredHeight = measuredHeight + vPadding; 1661 layoutTarget.measuredMinHeight = measuredMinHeight + vPadding; 1662 1663 _columnCount = savedColumnCount; 1664 _rowCount = savedRowCount; 1665 _horizontalGap = savedHorizontalGap; 1666 _verticalGap = savedVerticalGap; 1667 _columnWidth = savedColumnWidth; 1668 _rowHeight = savedRowHeight; 1669 } 1670 1671 /** 1672 * @private 1673 */ 1674 override public function getNavigationDestinationIndex(currentIndex:int, navigationUnit:uint, arrowKeysWrapFocus:Boolean):int 1675 { 1676 if (!target || target.numElements < 1) 1677 return -1; 1678 1679 var maxIndex:int = target.numElements - 1; 1680 1681 // Special case when nothing was previously selected 1682 if (currentIndex == -1) 1683 { 1684 if (navigationUnit == NavigationUnit.UP || navigationUnit == NavigationUnit.LEFT) 1685 return arrowKeysWrapFocus ? maxIndex : -1; 1686 1687 if (navigationUnit == NavigationUnit.DOWN || navigationUnit == NavigationUnit.RIGHT) 1688 return 0; 1689 } 1690 1691 // Make sure currentIndex is within range 1692 var inRows:Boolean = orientation == TileOrientation.ROWS; 1693 currentIndex = Math.max(0, Math.min(maxIndex, currentIndex)); 1694 1695 // Find the current column and row 1696 var currentRow:int; 1697 var currentColumn:int; 1698 if (inRows) 1699 { 1700 // Is the TileLayout initialized with valid values? 1701 if (columnCount == 0 || rowHeight + verticalGap == 0) 1702 return currentIndex; 1703 1704 currentRow = currentIndex / columnCount; 1705 currentColumn = currentIndex - currentRow * columnCount; 1706 } 1707 else 1708 { 1709 // Is the TileLayout initialized with valid values? 1710 if (rowCount == 0 || columnWidth + horizontalGap == 0) 1711 return currentIndex; 1712 1713 currentColumn = currentIndex / rowCount; 1714 currentRow = currentIndex - currentColumn * rowCount; 1715 } 1716 1717 var newRow:int = currentRow; 1718 var newColumn:int = currentColumn; 1719 1720 // Handle user input, almost all range checks are 1721 // performed after the calculations, at the end of the method. 1722 switch (navigationUnit) 1723 { 1724 case NavigationUnit.LEFT: 1725 { 1726 // If we are at the first column, can 1727 // we go to the previous element (last column, previous row)? 1728 if (newColumn == 0 && inRows && newRow > 0) 1729 { 1730 newRow--; 1731 newColumn = columnCount - 1; 1732 } 1733 else if (arrowKeysWrapFocus && newColumn == 0 && inRows && newRow == 0) 1734 { 1735 newRow = rowCount - 1; 1736 newColumn = columnCount - 1; 1737 } 1738 else 1739 newColumn--; 1740 break; 1741 } 1742 1743 case NavigationUnit.RIGHT: 1744 { 1745 // If we are at the last column, can 1746 // we go to the next element (first column, next row)? 1747 if (newColumn == columnCount - 1 && inRows && newRow < rowCount - 1) 1748 { 1749 newColumn = 0; 1750 newRow++; 1751 } 1752 else if (arrowKeysWrapFocus && newColumn == columnCount - 1 && inRows && newRow == rowCount - 1) 1753 { 1754 newColumn = 0; 1755 newRow = 0; 1756 } 1757 else 1758 newColumn++; 1759 break; 1760 } 1761 1762 case NavigationUnit.UP: 1763 { 1764 // If we are at the first row, can we 1765 // go to the previous element (previous column, last row)? 1766 if (newRow == 0 && !inRows && newColumn > 0) 1767 { 1768 newColumn--; 1769 newRow = rowCount - 1; 1770 } 1771 else if (arrowKeysWrapFocus && newRow == 0 && !inRows && newColumn == 0) 1772 { 1773 newColumn = columnCount - 1; 1774 newRow = rowCount - 1; 1775 } 1776 else 1777 newRow--; 1778 break; 1779 } 1780 1781 case NavigationUnit.DOWN: 1782 { 1783 // If we are at the last row, can we 1784 // go to the next element (next column, first row)? 1785 if (newRow == rowCount - 1 && !inRows && newColumn < columnCount - 1) 1786 { 1787 newColumn++; 1788 newRow = 0; 1789 } 1790 else if (arrowKeysWrapFocus && newRow == rowCount - 1 && !inRows && newColumn == columnCount - 1) 1791 { 1792 newColumn = 0; 1793 newRow = 0; 1794 } 1795 else 1796 newRow++; 1797 break; 1798 } 1799 1800 case NavigationUnit.PAGE_UP: 1801 case NavigationUnit.PAGE_DOWN: 1802 { 1803 // Ensure we have a valid scrollRect as we use it for calculations below 1804 var scrollRect:Rectangle = getScrollRect(); 1805 if (!scrollRect) 1806 scrollRect = new Rectangle(0, 0, target.contentWidth, target.contentHeight); 1807 1808 if (inRows) 1809 { 1810 var firstVisibleRow:int = Math.ceil(scrollRect.top / (rowHeight + verticalGap)); 1811 var lastVisibleRow:int = Math.floor(scrollRect.bottom / (rowHeight + verticalGap)); 1812 1813 if (navigationUnit == NavigationUnit.PAGE_UP) 1814 { 1815 // Is the current row visible, somewhere in the middle of the scrollRect? 1816 if (firstVisibleRow < currentRow && currentRow <= lastVisibleRow) 1817 newRow = firstVisibleRow; 1818 else 1819 newRow = 2 * firstVisibleRow - lastVisibleRow; 1820 } 1821 else 1822 { 1823 // Is the current row visible, somewhere in the middle of the scrollRect? 1824 if (firstVisibleRow <= currentRow && currentRow < lastVisibleRow) 1825 newRow = lastVisibleRow; 1826 else 1827 newRow = 2 * lastVisibleRow - firstVisibleRow; 1828 } 1829 } 1830 else 1831 { 1832 var firstVisibleColumn:int = Math.ceil(scrollRect.left / (columnWidth + horizontalGap)); 1833 var lastVisibleColumn:int = Math.floor(scrollRect.right / (columnWidth + horizontalGap)); 1834 1835 if (navigationUnit == NavigationUnit.PAGE_UP) 1836 { 1837 // Is the current column visible, somewhere in the middle of the scrollRect? 1838 if (firstVisibleColumn < currentColumn && currentColumn <= lastVisibleColumn) 1839 newColumn = firstVisibleColumn; 1840 else 1841 newColumn = 2 * firstVisibleColumn - lastVisibleColumn; 1842 } 1843 else 1844 { 1845 // Is the current column visible, somewhere in the middle of the scrollRect? 1846 if (firstVisibleColumn <= currentColumn && currentColumn < lastVisibleColumn) 1847 newColumn = lastVisibleColumn; 1848 else 1849 newColumn = 2 * lastVisibleColumn - firstVisibleColumn; 1850 } 1851 } 1852 break; 1853 } 1854 default: return super.getNavigationDestinationIndex(currentIndex, navigationUnit, arrowKeysWrapFocus); 1855 } 1856 1857 // Make sure rows and columns are within range 1858 newRow = Math.max(0, Math.min(rowCount - 1, newRow)); 1859 newColumn = Math.max(0, Math.min(columnCount - 1, newColumn)); 1860 1861 // Calculate the new index based on orientation 1862 if (inRows) 1863 { 1864 // Make sure we don't return an index for an empty space in the last row. 1865 // newRow is guaranteed to be greater than zero: 1866 1867 if (newRow == rowCount - 1) 1868 { 1869 // Step 1: We can end up at the empty space in the last row if we moved right from 1870 // the last item. 1871 if (currentIndex == maxIndex && newColumn > currentColumn) 1872 newColumn = currentColumn; 1873 1874 // Step 2: We can end up at the empty space in the last row if we moved down from 1875 // the previous row. 1876 if (newColumn > maxIndex - columnCount * (rowCount - 1)) 1877 newRow--; 1878 } 1879 1880 return newRow * columnCount + newColumn; 1881 } 1882 else 1883 { 1884 // Make sure we don't return an index for an empty space in the last column. 1885 // newColumn is guaranteed to be greater than zero: 1886 1887 if (newColumn == columnCount - 1) 1888 { 1889 // Step 1: We can end up at the empty space in the last column if we moved down from 1890 // the last item. 1891 if (currentIndex == maxIndex && newRow > currentRow) 1892 newRow = currentRow; 1893 1894 // Step 2: We can end up at the empty space in the last column if we moved right from 1895 // the previous column. 1896 if (newRow > maxIndex - rowCount * (columnCount - 1)) 1897 newColumn--; 1898 } 1899 1900 return newColumn * rowCount + newRow; 1901 } 1902 } 1903 1904 /** 1905 * @private 1906 */ 1907 override public function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void 1908 { 1909 var layoutTarget:GroupBase = target; 1910 if (!layoutTarget) 1911 return; 1912 1913 calculateDisplayParameters(unscaledWidth, unscaledHeight); 1914 if (useVirtualLayout) 1915 updateVirtualLayout(unscaledWidth, unscaledHeight); // re-calculateDisplayParameters() 1916 1917 // Upper right hand corner of first (visibleStartIndex) tile/cell 1918 var xPos:Number = visibleStartX; // paddingLeft if useVirtualLayout=false 1919 var yPos:Number = visibleStartY; // paddingTop if useVirtualLayout=false 1920 1921 // Use MajorDelta when moving along the major axis 1922 var xMajorDelta:Number; 1923 var yMajorDelta:Number; 1924 1925 // Use MinorDelta when moving along the minor axis 1926 var xMinorDelta:Number; 1927 var yMinorDelta:Number; 1928 1929 // Use counter and counterLimit to track when to move along the minor axis 1930 var counter:int = 0; 1931 var counterLimit:int; 1932 1933 // Setup counterLimit and deltas based on orientation 1934 if (orientation == TileOrientation.ROWS) 1935 { 1936 counterLimit = _columnCount; 1937 xMajorDelta = _columnWidth + _horizontalGap; 1938 xMinorDelta = 0; 1939 yMajorDelta = 0; 1940 yMinorDelta = _rowHeight + _verticalGap; 1941 } 1942 else 1943 { 1944 counterLimit = _rowCount; 1945 xMajorDelta = 0; 1946 xMinorDelta = _columnWidth + _horizontalGap; 1947 yMajorDelta = _rowHeight + _verticalGap; 1948 yMinorDelta = 0; 1949 } 1950 1951 for (var index:int = visibleStartIndex; index <= visibleEndIndex; index++) 1952 { 1953 var el:ILayoutElement = null; 1954 if (useVirtualLayout) 1955 { 1956 el = layoutTarget.getVirtualElementAt(index); 1957 if (el is IVisualElement) // see updateVirtualLayout 1958 IVisualElement(el).visible = true; 1959 } 1960 else 1961 el = layoutTarget.getElementAt(index); 1962 1963 if (!el || !el.includeInLayout) 1964 continue; 1965 1966 // To calculate the cell extents as integers, first calculate 1967 // the extents and then use Math.round() 1968 var cellX:int = Math.round(xPos); 1969 var cellY:int = Math.round(yPos); 1970 var cellWidth:int = Math.round(xPos + _columnWidth) - cellX; 1971 var cellHeight:int = Math.round(yPos + _rowHeight) - cellY; 1972 1973 sizeAndPositionElement(el, cellX, cellY, cellWidth, cellHeight); 1974 1975 // Move along the major axis 1976 xPos += xMajorDelta; 1977 yPos += yMajorDelta; 1978 1979 // Move along the minor axis 1980 if (++counter >= counterLimit) 1981 { 1982 counter = 0; 1983 if (orientation == TileOrientation.ROWS) 1984 { 1985 xPos = paddingLeft; 1986 yPos += yMinorDelta; 1987 } 1988 else 1989 { 1990 xPos += xMinorDelta; 1991 yPos = paddingTop; 1992 } 1993 } 1994 } 1995 1996 var hPadding:Number = paddingLeft + paddingRight; 1997 var vPadding:Number = paddingTop + paddingBottom; 1998 1999 // Make sure that if the content spans partially over a pixel to the right/bottom, 2000 // the content size includes the whole pixel. 2001 layoutTarget.setContentSize(Math.ceil(_columnCount * (_columnWidth + _horizontalGap) - _horizontalGap) + hPadding, 2002 Math.ceil(_rowCount * (_rowHeight + _verticalGap) - _verticalGap) + vPadding); 2003 2004 // Reset the cache 2005 if (!useVirtualLayout) 2006 _tileWidthCached = _tileHeightCached = NaN; 2007 _numElementsCached = -1; 2008 2009 // No getVirtualElementAt() during measure, see calculateVirtualTileSize() 2010 if (useVirtualLayout) 2011 visibleStartIndex = visibleEndIndex = -1; 2012 2013 // If actual values have chnaged, notify listeners 2014 dispatchEventsForActualValueChanges(); 2015 } 2016 2017 /** 2018 * @private 2019 */ 2020 override public function getElementBounds(index:int):Rectangle 2021 { 2022 if (!useVirtualLayout) 2023 return super.getElementBounds(index); 2024 2025 var g:GroupBase = GroupBase(target); 2026 if (!g || (index < 0) || (index >= g.numElements)) 2027 return null; 2028 2029 var col:int; 2030 var row:int; 2031 if (orientation == TileOrientation.ROWS) 2032 { 2033 col = index % _columnCount; 2034 row = index / _columnCount; 2035 } 2036 else 2037 { 2038 col = index / _rowCount; 2039 row = index % _rowCount 2040 } 2041 return new Rectangle(leftEdge(col), topEdge(row), _columnWidth, _rowHeight); 2042 } 2043 2044 /** 2045 * @private 2046 */ 2047 override protected function getElementBoundsLeftOfScrollRect(scrollRect:Rectangle):Rectangle 2048 { 2049 var bounds:Rectangle = new Rectangle(); 2050 // Find the column that spans or is to the left of the scrollRect left edge. 2051 var column:int = Math.floor((scrollRect.left - 1 - paddingLeft) / (_columnWidth + _horizontalGap)); 2052 bounds.left = leftEdge(column); 2053 bounds.right = rightEdge(column); 2054 return bounds; 2055 } 2056 2057 /** 2058 * @private 2059 */ 2060 override protected function getElementBoundsRightOfScrollRect(scrollRect:Rectangle):Rectangle 2061 { 2062 var bounds:Rectangle = new Rectangle(); 2063 // Find the column that spans or is to the right of the scrollRect right edge. 2064 var column:int = Math.floor(((scrollRect.right + 1 + _horizontalGap) - paddingLeft) / (_columnWidth + _horizontalGap)); 2065 bounds.left = leftEdge(column); 2066 bounds.right = rightEdge(column); 2067 return bounds; 2068 } 2069 2070 /** 2071 * @private 2072 */ 2073 override protected function getElementBoundsAboveScrollRect(scrollRect:Rectangle):Rectangle 2074 { 2075 var bounds:Rectangle = new Rectangle(); 2076 // Find the row that spans or is above the scrollRect top edge 2077 var row:int = Math.floor((scrollRect.top - 1 - paddingTop) / (_rowHeight + _verticalGap)); 2078 bounds.top = topEdge(row); 2079 bounds.bottom = bottomEdge(row); 2080 return bounds; 2081 } 2082 2083 /** 2084 * @private 2085 */ 2086 override protected function getElementBoundsBelowScrollRect(scrollRect:Rectangle):Rectangle 2087 { 2088 var bounds:Rectangle = new Rectangle(); 2089 // Find the row that spans or is below the scrollRect bottom edge 2090 var row:int = Math.floor(((scrollRect.bottom + 1 + _verticalGap) - paddingTop) / (_rowHeight + _verticalGap)); 2091 bounds.top = topEdge(row); 2092 bounds.bottom = bottomEdge(row); 2093 return bounds; 2094 } 2095 2096 /** 2097 * @private 2098 */ 2099 override public function elementAdded(index:int):void 2100 { 2101 invalidateTargetSizeAndDisplayList(); 2102 } 2103 2104 /** 2105 * @private 2106 */ 2107 override public function elementRemoved(index:int):void 2108 { 2109 invalidateTargetSizeAndDisplayList(); 2110 } 2111 2112 //-------------------------------------------------------------------------- 2113 // 2114 // Drop methods 2115 // 2116 //-------------------------------------------------------------------------- 2117 2118 /** 2119 * @private 2120 * Calculates the column and row and returns the corresponding cell index. 2121 * Index may be out of range if there's no element for the cell. 2122 */ 2123 private function calculateDropCellIndex(x:Number, y:Number):Array 2124 { 2125 var xStart:Number = x - paddingLeft; 2126 var yStart:Number = y - paddingTop; 2127 var column:int = Math.floor(xStart / (_columnWidth + _horizontalGap)); 2128 var row:int = Math.floor(yStart / (_rowHeight + _verticalGap)); 2129 2130 // Check whether x is closer to left column or right column: 2131 var midColumnLine:Number; 2132 var midRowLine:Number 2133 2134 var rowOrientation:Boolean = orientation == TileOrientation.ROWS; 2135 if (rowOrientation) 2136 { 2137 // Mid-line is at the middle of the cell 2138 midColumnLine = (column + 1) * (_columnWidth + _horizontalGap) - _horizontalGap - _columnWidth / 2; 2139 2140 // Mid-line is at the middle of the gap between the rows 2141 midRowLine = (row + 1) * (_rowHeight + _verticalGap) - _verticalGap / 2; 2142 } 2143 else 2144 { 2145 // Mid-line is at the middle of the gap between the columns 2146 midColumnLine = (column + 1) * (_columnWidth + _horizontalGap) - _horizontalGap / 2; 2147 2148 // Mid-line is at the middle of the cell 2149 midRowLine = (row + 1) * (_rowHeight + _verticalGap) - _verticalGap - _rowHeight / 2; 2150 } 2151 2152 if (xStart > midColumnLine) 2153 column++; 2154 if (yStart > midRowLine) 2155 row++; 2156 2157 // Limit row and column, if any one is too far from the drop location 2158 // And there is white space 2159 if (column > _columnCount || row > _rowCount) 2160 { 2161 row = _rowCount; 2162 column = _columnCount; 2163 } 2164 2165 if (column < 0) 2166 column = 0; 2167 if (row < 0) 2168 row = 0; 2169 2170 if (rowOrientation) 2171 { 2172 if (row >= _rowCount) 2173 row = _rowCount - 1; 2174 } 2175 else 2176 { 2177 if (column >= _columnCount) 2178 column = _columnCount - 1; 2179 } 2180 return [row, column]; 2181 } 2182 2183 /** 2184 * @inheritDoc 2185 * 2186 * @langversion 3.0 2187 * @playerversion Flash 10 2188 * @playerversion AIR 1.5 2189 * @productversion Flex 4 2190 */ 2191 override protected function calculateDropIndex(x:Number, y:Number):int 2192 { 2193 var result:Array = calculateDropCellIndex(x, y); 2194 var row:int = result[0]; 2195 var column:int = result[1]; 2196 var index:int; 2197 2198 if (orientation == TileOrientation.ROWS) 2199 index = row * _columnCount + column; 2200 else 2201 index = column * _rowCount + row; 2202 2203 var count:int = calculateElementCount(); 2204 _numElementsCached = -1; // Reset the cache 2205 2206 if (index > count) 2207 index = count; 2208 return index; 2209 } 2210 2211 /** 2212 * @inheritDoc 2213 * 2214 * @langversion 3.0 2215 * @playerversion Flash 10 2216 * @playerversion AIR 1.5 2217 * @productversion Flex 4 2218 */ 2219 override protected function calculateDropIndicatorBounds(dropLocation:DropLocation):Rectangle 2220 { 2221 var result:Array = calculateDropCellIndex(dropLocation.dropPoint.x, dropLocation.dropPoint.y); 2222 var row:int = result[0]; 2223 var column:int = result[1]; 2224 2225 var rowOrientation:Boolean = orientation == TileOrientation.ROWS; 2226 var count:int = calculateElementCount(); 2227 _numElementsCached = -1; // Reset the cache 2228 2229 if (rowOrientation) 2230 { 2231 // The last row may be only partially full, 2232 // don't drop beyond the last column. 2233 if (row * _columnCount + column > count) 2234 column = count - (_rowCount - 1) * _columnCount; 2235 } 2236 else 2237 { 2238 // The last column may be only partially full, 2239 // don't drop beyond the last row. 2240 if (column * _rowCount + row > count) 2241 row = count - (_columnCount - 1) * _rowCount; 2242 } 2243 2244 var x:Number; 2245 var y:Number; 2246 var dropIndicatorElement:IVisualElement; 2247 var emptySpace:Number; // empty space between the elements 2248 2249 // Start with the dropIndicator dimensions, in case it's not 2250 // an IVisualElement 2251 var width:Number = dropIndicator.width; 2252 var height:Number = dropIndicator.height; 2253 2254 if (rowOrientation) 2255 { 2256 emptySpace = (0 < _horizontalGap ) ? _horizontalGap : 0; 2257 var emptySpaceLeft:Number = column * (_columnWidth + _horizontalGap) - emptySpace; 2258 2259 // Special case - if we have negative gap and we're the last column, 2260 // adjust the emptySpaceLeft 2261 if (_horizontalGap < 0 && (column == _columnCount || count == dropLocation.dropIndex)) 2262 emptySpaceLeft -= _horizontalGap; 2263 2264 width = emptySpace; 2265 height = _rowHeight; 2266 // Special case - if we have negative gap and we're not the last 2267 // row, adjust the height 2268 if (_verticalGap < 0 && row < _rowCount - 1) 2269 height += _verticalGap + 1; 2270 2271 if (dropIndicator is IVisualElement) 2272 { 2273 dropIndicatorElement = IVisualElement(dropIndicator); 2274 width = Math.max(Math.min(width, 2275 dropIndicatorElement.getMaxBoundsWidth(false)), 2276 dropIndicatorElement.getMinBoundsWidth(false)); 2277 } 2278 2279 x = emptySpaceLeft + Math.round((emptySpace - width) / 2) + paddingLeft; 2280 // Allow 1 pixel overlap with container border 2281 x = Math.max(-1, Math.min(target.contentWidth - width + 1, x)); 2282 2283 y = row * (_rowHeight + _verticalGap) + paddingTop; 2284 } 2285 else 2286 { 2287 emptySpace = (0 < _verticalGap ) ? _verticalGap : 0; 2288 var emptySpaceTop:Number = row * (_rowHeight + _verticalGap) - emptySpace; 2289 2290 // Special case - if we have negative gap and we're the last column, 2291 // adjust the emptySpaceLeft 2292 if (_verticalGap < 0 && (row == _rowCount || count == dropLocation.dropIndex)) 2293 emptySpaceTop -= _verticalGap; 2294 2295 width = _columnWidth; 2296 height = emptySpace; 2297 // Special case - if we have negative gap and we're not the last 2298 // column, adjust the width 2299 if (_horizontalGap < 0 && column < _columnCount - 1) 2300 width += _horizontalGap + 1; 2301 2302 if (dropIndicator is IVisualElement) 2303 { 2304 dropIndicatorElement = IVisualElement(dropIndicator); 2305 height = Math.max(Math.min(emptySpace, 2306 dropIndicatorElement.getMaxBoundsWidth(false)), 2307 dropIndicatorElement.getMinBoundsWidth(false)); 2308 } 2309 2310 x = column * (_columnWidth + _horizontalGap) + paddingLeft; 2311 2312 y = emptySpaceTop + Math.round((emptySpace - height) / 2) + paddingTop; 2313 // Allow 1 pixel overlap with container border 2314 y = Math.max(-1, Math.min(target.contentHeight - height + 1, y)); 2315 } 2316 2317 return new Rectangle(x, y, width, height); 2318 } 2319 2320 /** 2321 * @private 2322 * Helper function to return the row and column of the cell 2323 * containing the x/y point. The associated index may be out 2324 * of range if there's no element for the cell. 2325 */ 2326 private function calculateCellIndex(x:Number, y:Number):Array 2327 { 2328 var xStart:Number = x - paddingLeft; 2329 var yStart:Number = y - paddingTop; 2330 var column:int = Math.floor(xStart / (_columnWidth + _horizontalGap)); 2331 var row:int = Math.floor(yStart / (_rowHeight + _verticalGap)); 2332 2333 // Make sure column and row numbers are valid 2334 if (column < 0) 2335 column = 0; 2336 if (column >= _columnCount) 2337 column = _columnCount - 1; 2338 if (row < 0) 2339 row = 0; 2340 if (row >= _rowCount) 2341 row = _rowCount - 1; 2342 2343 return [row, column]; 2344 } 2345 2346 2347 /** 2348 * @private 2349 * Identifies the element which has its "compare point" located closest 2350 * to the specified position. 2351 */ 2352 override mx_internal function getElementNearestScrollPosition( 2353 position:Point, 2354 elementComparePoint:String = "center"):int 2355 { 2356 // Determine which cell contains the point 2357 var result:Array = calculateCellIndex(position.x, position.y); 2358 var row:int = result[0]; 2359 var column:int = result[1]; 2360 2361 var maxRow:int = _rowCount-1; 2362 var maxColumn:int = _columnCount-1; 2363 2364 // create a rectangle of the element bounds 2365 var bounds:Rectangle = 2366 new Rectangle(leftEdge(column), topEdge(row), _columnWidth + _horizontalGap, _rowHeight + _verticalGap); 2367 2368 const TOP_LEFT:int = 0; 2369 const TOP_RIGHT:int = 1; 2370 const BOTTOM_LEFT:int = 2; 2371 const BOTTOM_RIGHT:int = 3; 2372 2373 // Determine the quadrant of the element/cell which contains the position point. 2374 var quadrant:int = TOP_LEFT; 2375 if (position.x > bounds.left + bounds.width/2) 2376 quadrant += TOP_RIGHT; 2377 if (position.y > bounds.top + bounds.height/2) 2378 quadrant += BOTTOM_LEFT; 2379 2380 var index:int; 2381 if (orientation == TileOrientation.ROWS) 2382 index = row * _columnCount + column; 2383 else 2384 index = column * _rowCount + row; 2385 2386 var g:GroupBase = GroupBase(target); 2387 if (index >= g.numElements) 2388 { 2389 // TODO (eday): two-dimensional item snapping will require more sophisticated cell detection 2390 // if the position is beyond the content. For now (while we only support one-dimensional 2391 // snapping), using the last element will work fine. 2392 return g.numElements - 1; 2393 } 2394 2395 // Depending on which point of the element is to be compared with, and on which 2396 // quadrant of the element contains the position, we may adjust row/column to 2397 // points to an adjacent cell. 2398 switch (elementComparePoint) 2399 { 2400 case "topLeft": 2401 if (quadrant == TOP_RIGHT && column < maxColumn) 2402 column++; 2403 else if (quadrant == BOTTOM_LEFT && row < maxRow) 2404 row++; 2405 else if (quadrant == BOTTOM_RIGHT && row < maxRow && column < maxColumn) 2406 { 2407 row++; 2408 column++; 2409 } 2410 break; 2411 case "topRight": 2412 if (quadrant == TOP_LEFT && column > 0) 2413 column--; 2414 else if (quadrant == BOTTOM_LEFT && column > 0 && row < maxRow) 2415 { 2416 column--; 2417 row++; 2418 } 2419 else if (quadrant == BOTTOM_RIGHT && row < maxRow) 2420 row++; 2421 break; 2422 case "bottomLeft": 2423 if (quadrant == TOP_LEFT && row > 0) 2424 row--; 2425 else if (quadrant == TOP_RIGHT && row > 0 && column < maxColumn) 2426 { 2427 row--; 2428 column++; 2429 } 2430 else if (quadrant == BOTTOM_RIGHT && column < maxColumn) 2431 column++; 2432 break; 2433 case "bottomRight": 2434 if (quadrant == TOP_LEFT && column > 0 && row > 0) 2435 { 2436 column--; 2437 row--; 2438 } 2439 else if (quadrant == TOP_RIGHT && row > 0) 2440 row--; 2441 else if (quadrant == BOTTOM_LEFT && column > 0) 2442 column-- 2443 break; 2444 case "center": 2445 // for center snapping, "index" is already always the cell with 2446 // its center closest to the specified position 2447 break; 2448 } 2449 2450 var newIndex:int; 2451 if (orientation == TileOrientation.ROWS) 2452 newIndex = row * _columnCount + column; 2453 else 2454 newIndex = column * _rowCount + row; 2455 if (newIndex < g.numElements) 2456 index = newIndex; 2457 2458 return index; 2459 } 2460 2461} 2462} 2463