Index: ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_collection_view_layout.mm |
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_collection_view_layout.mm b/ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_collection_view_layout.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..08522d91ee1d1767f7e6b701e03a2fda5523ac2f |
--- /dev/null |
+++ b/ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_collection_view_layout.mm |
@@ -0,0 +1,153 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_collection_view_layout.h" |
+ |
+#include <algorithm> |
+ |
+#include "base/logging.h" |
+#include "base/mac/scoped_nsobject.h" |
+ |
+namespace { |
+const CGFloat minWidthOfTab = 200; |
+const CGFloat kInterTabSpacing = 16; |
+const UIEdgeInsets kCollectionViewEdgeInsets = {16, 16, 16, 16}; |
+const CGFloat kMaxSizeAsAFactorOfBounds = 0.5; |
+const CGFloat kMinCellHeightWidthRatio = 0.6; |
+const CGFloat kMaxCellHeightWidthRatio = 1.8; |
+} |
+ |
+@implementation TabSwitcherPanelCollectionViewLayout { |
+ // Keeps track of the inserted and deleted index paths. |
+ base::scoped_nsobject<NSMutableArray> _deletedIndexPaths; |
+ base::scoped_nsobject<NSMutableArray> _insertedIndexPaths; |
+} |
+ |
+- (int)maxRowCountWithColumnCount:(int)columnCount inBounds:(CGSize)boundsSize { |
+ int cellWidth = (boundsSize.width / columnCount) - (kInterTabSpacing * 2.0); |
+ int minCellHeight = cellWidth * kMinCellHeightWidthRatio; |
+ return boundsSize.height / (minCellHeight + (kInterTabSpacing * 2.0)); |
+} |
+ |
+- (void)updateLayoutWithBounds:(CGSize)boundsSize { |
+ // Ignore initial call to |updateLayoutWithBounds| when the frame of the |
+ // collection view is CGRectZero, because it creates very small cells with |
+ // broken constraints. |
+ if (boundsSize.height == 0 && boundsSize.width == 0) |
+ return; |
+ int tabCount = [[self collectionView] numberOfItemsInSection:0]; |
+ // Early return because there's nothing to layout. |
+ if (tabCount == 0) |
+ return; |
+ |
+ int numberOfColumns = 0; |
+ int numberOfRows = 0; |
+ |
+ int maxNumberOfColums = static_cast<int>( |
+ floor(boundsSize.width / (minWidthOfTab + kInterTabSpacing * 2.0))); |
+ // No need to have more columns than tabs. |
+ maxNumberOfColums = std::min(maxNumberOfColums, tabCount); |
+ |
+ if ([self maxRowCountWithColumnCount:maxNumberOfColums inBounds:boundsSize] * |
+ maxNumberOfColums < |
+ tabCount) { |
+ // It is impossible for all the tabs to be shown on screen at once. |
+ // Layout the tabs using the highest density possible, i.e. using the |
+ // maximum number of columns. |
+ numberOfColumns = maxNumberOfColums; |
+ numberOfRows = |
+ [self maxRowCountWithColumnCount:maxNumberOfColums inBounds:boundsSize]; |
+ } else { |
+ // Find the most squarish configuration that allows showing all the tabs. |
+ // A squarish configuration is a layout were the number of rows and columns |
+ // are roughly equal. |
+ |
+ // |bestScore| contains abs(rowCount - columnCount). |
+ // The lower |bestScore| is, the better the configuration is. |
+ int bestScore = INT_MAX; |
+ |
+ int loopStart; |
+ int loopEnd; |
+ int loopDirection; |
+ if (boundsSize.width > boundsSize.height) { |
+ // In landscape, consider in priority layouts with a large number of |
+ // columns. |
+ loopStart = maxNumberOfColums; |
+ loopEnd = 0; |
+ loopDirection = -1; |
+ } else { |
+ // In landscape, consider in priority layouts with a large number of rows, |
+ // i.e. a small number of columns. |
+ loopStart = 1; |
+ loopEnd = maxNumberOfColums + 1; |
+ loopDirection = 1; |
+ } |
+ |
+ int columnCountIterator = loopStart; |
+ while (columnCountIterator != loopEnd) { |
+ // Find the minimum number of rows needed to show |tabCount| tab in |
+ // |columnCount| columns. |
+ int maxRowCount = [self maxRowCountWithColumnCount:columnCountIterator |
+ inBounds:boundsSize]; |
+ int idealRowCount = static_cast<int>( |
+ ceil(static_cast<float>(tabCount) / columnCountIterator)); |
+ if (idealRowCount <= maxRowCount) { |
+ int score = abs(idealRowCount - columnCountIterator); |
+ if (score < bestScore) { |
+ bestScore = score; |
+ numberOfColumns = columnCountIterator; |
+ numberOfRows = idealRowCount; |
+ } |
+ } |
+ columnCountIterator += loopDirection; |
+ } |
+ DCHECK_NE(bestScore, INT_MAX); |
+ } |
+ |
+ DCHECK_NE(numberOfColumns, 0); |
+ DCHECK_NE(numberOfRows, 0); |
+ |
+ // Compute the size of the cells. |
+ CGFloat horizontalFreeSpace = |
+ boundsSize.width - (kInterTabSpacing * 2 * (numberOfColumns - 1)) - |
+ kCollectionViewEdgeInsets.left - kCollectionViewEdgeInsets.right; |
+ CGFloat verticalFreeSpace = |
+ boundsSize.height - (kInterTabSpacing * 2 * (numberOfRows - 1)) - |
+ kCollectionViewEdgeInsets.top - kCollectionViewEdgeInsets.bottom; |
+ CGSize newCellSize = CGSizeMake(horizontalFreeSpace / numberOfColumns, |
+ verticalFreeSpace / numberOfRows); |
+ |
+ // The cells must not be larger than half of the bounds because the @1x |
+ // snapshots would look blurry on retina screens. |
+ newCellSize.width = |
+ std::min(newCellSize.width, boundsSize.width * kMaxSizeAsAFactorOfBounds); |
+ newCellSize.height = std::min(newCellSize.height, |
+ boundsSize.height * kMaxSizeAsAFactorOfBounds); |
+ |
+ // Avoid having cells be too narrow. |
+ newCellSize.height = std::min(newCellSize.height, |
+ newCellSize.width * kMaxCellHeightWidthRatio); |
+ |
+ [self setItemSize:newCellSize]; |
+ |
+ [self setMinimumInteritemSpacing:kInterTabSpacing]; |
+ [self setMinimumLineSpacing:kInterTabSpacing * 2]; |
+ |
+ bool forceVerticalCentering = numberOfRows == 1; |
+ if (forceVerticalCentering) { |
+ UIEdgeInsets insets = kCollectionViewEdgeInsets; |
+ insets.top = (boundsSize.height - newCellSize.height) / 2.0; |
+ insets.bottom = (boundsSize.height - newCellSize.height) / 2.0; |
+ [self setSectionInset:insets]; |
+ } else { |
+ [self setSectionInset:kCollectionViewEdgeInsets]; |
+ } |
+} |
+ |
+- (void)prepareLayout { |
+ [super prepareLayout]; |
+ [self updateLayoutWithBounds:[[self collectionView] bounds].size]; |
+} |
+ |
+@end |