blob: 16895e7702917c4391e8a1b21acbd7cba09c45cb [file] [log] [blame]
// Copyright 2018 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_grid/grid/grid_layout.h"
#import "ios/chrome/browser/ui/tab_grid/grid/grid_constants.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface GridLayout ()
@property(nonatomic, strong) NSArray<NSIndexPath*>* indexPathsOfDeletingItems;
@property(nonatomic, strong) NSArray<NSIndexPath*>* indexPathsOfInsertingItems;
@end
@implementation GridLayout
@synthesize animatesItemUpdates = _animatesItemUpdates;
@synthesize indexPathsOfDeletingItems = _indexPathsOfDeletingItems;
@synthesize indexPathsOfInsertingItems = _indexPathsOfInsertingItems;
- (instancetype)init {
if (self = [super init]) {
_animatesItemUpdates = YES;
}
return self;
}
#pragma mark - UICollectionViewLayout
// This is called whenever the layout is invalidated, including during rotation.
// Resizes item, margins, and spacing to fit new size classes and width.
- (void)prepareLayout {
[super prepareLayout];
UIUserInterfaceSizeClass horizontalSizeClass =
self.collectionView.traitCollection.horizontalSizeClass;
UIUserInterfaceSizeClass verticalSizeClass =
self.collectionView.traitCollection.verticalSizeClass;
CGFloat width = CGRectGetWidth(self.collectionView.bounds);
if (horizontalSizeClass == UIUserInterfaceSizeClassCompact &&
verticalSizeClass == UIUserInterfaceSizeClassCompact) {
self.itemSize = kGridCellSizeSmall;
if (width < kGridLayoutCompactCompactLimitedWidth) {
self.sectionInset = kGridLayoutInsetsCompactCompactLimitedWidth;
self.minimumLineSpacing =
kGridLayoutLineSpacingCompactCompactLimitedWidth;
} else {
self.sectionInset = kGridLayoutInsetsCompactCompact;
self.minimumLineSpacing = kGridLayoutLineSpacingCompactCompact;
}
} else if (horizontalSizeClass == UIUserInterfaceSizeClassCompact &&
verticalSizeClass == UIUserInterfaceSizeClassRegular) {
if (width < kGridLayoutCompactRegularLimitedWidth) {
self.itemSize = kGridCellSizeSmall;
self.sectionInset = kGridLayoutInsetsCompactRegularLimitedWidth;
self.minimumLineSpacing =
kGridLayoutLineSpacingCompactRegularLimitedWidth;
} else {
self.itemSize = kGridCellSizeMedium;
self.sectionInset = kGridLayoutInsetsCompactRegular;
self.minimumLineSpacing = kGridLayoutLineSpacingCompactRegular;
}
} else if (horizontalSizeClass == UIUserInterfaceSizeClassRegular &&
verticalSizeClass == UIUserInterfaceSizeClassCompact) {
self.itemSize = kGridCellSizeSmall;
self.sectionInset = kGridLayoutInsetsRegularCompact;
self.minimumLineSpacing = kGridLayoutLineSpacingRegularCompact;
} else {
self.itemSize = kGridCellSizeLarge;
self.sectionInset = kGridLayoutInsetsRegularRegular;
self.minimumLineSpacing = kGridLayoutLineSpacingRegularRegular;
}
}
- (void)prepareForCollectionViewUpdates:
(NSArray<UICollectionViewUpdateItem*>*)updateItems {
[super prepareForCollectionViewUpdates:updateItems];
// Track which items in this update are explicitly being deleted or inserted.
NSMutableArray<NSIndexPath*>* deletingItems =
[NSMutableArray arrayWithCapacity:updateItems.count];
NSMutableArray<NSIndexPath*>* insertingItems =
[NSMutableArray arrayWithCapacity:updateItems.count];
for (UICollectionViewUpdateItem* item in updateItems) {
switch (item.updateAction) {
case UICollectionUpdateActionDelete:
[deletingItems addObject:item.indexPathBeforeUpdate];
break;
case UICollectionUpdateActionInsert:
[insertingItems addObject:item.indexPathAfterUpdate];
break;
default:
break;
}
}
self.indexPathsOfDeletingItems = [deletingItems copy];
self.indexPathsOfInsertingItems = [insertingItems copy];
}
- (UICollectionViewLayoutAttributes*)
finalLayoutAttributesForDisappearingItemAtIndexPath:
(NSIndexPath*)itemIndexPath {
// Return initial layout if animations are disabled.
if (!self.animatesItemUpdates) {
return [self layoutAttributesForItemAtIndexPath:itemIndexPath];
}
// Note that this method is called for any item whose index path changing from
// |itemIndexPath|, which includes any items that were in the layout and whose
// index path is changing. For an item whose index path is changing, this
// method is called before
// -initialLayoutAttributesForAppearingItemAtIndexPath:
UICollectionViewLayoutAttributes* attributes = [[super
finalLayoutAttributesForDisappearingItemAtIndexPath:itemIndexPath] copy];
// Disappearing items that aren't being deleted just use the default
// attributes.
if (![self.indexPathsOfDeletingItems containsObject:itemIndexPath]) {
return attributes;
}
// Cells being deleted scale to 0, and are z-positioned behind all others.
// (Note that setting the zIndex here actually has no effect, despite what is
// implied in the UIKit documentation).
attributes.zIndex = -10;
// Scaled down to 0% (or near enough).
CGAffineTransform transform =
CGAffineTransformScale(attributes.transform, /*sx=*/0.01, /*sy=*/0.01);
attributes.transform = transform;
// Fade out.
attributes.alpha = 0.0;
return attributes;
}
- (UICollectionViewLayoutAttributes*)
initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath*)itemIndexPath {
// Return final layout if animations are disabled.
if (!self.animatesItemUpdates) {
return [self layoutAttributesForItemAtIndexPath:itemIndexPath];
}
// Note that this method is called for any item whose index path is becoming
// |itemIndexPath|, which includes any items that were in the layout but whose
// index path is changing. For an item whose index path is changing, this
// method is called after
// -finalLayoutAttributesForDisappearingItemAtIndexPath:
UICollectionViewLayoutAttributes* attributes = [[super
initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath] copy];
// Appearing items that aren't being inserted just use the default
// attributes.
if (![self.indexPathsOfInsertingItems containsObject:itemIndexPath]) {
return attributes;
}
// TODO(crbug.com/820410) : Polish the animation, and put constants where they
// belong.
// Cells being inserted start faded out, scaled down, and drop downwards
// slightly.
attributes.alpha = 0.0;
CGAffineTransform transform =
CGAffineTransformScale(attributes.transform, /*sx=*/0.9, /*sy=*/0.9);
transform = CGAffineTransformTranslate(transform, /*tx=*/0,
/*ty=*/attributes.size.height * 0.1);
attributes.transform = transform;
return attributes;
}
- (void)finalizeCollectionViewUpdates {
self.indexPathsOfDeletingItems = @[];
self.indexPathsOfInsertingItems = @[];
}
@end
@implementation GridReorderingLayout
#pragma mark - UICollectionViewLayout
// Both -layoutAttributesForElementsInRect: and
// -layoutAttributesForItemAtIndexPath: need to be overridden to change the
// default layout attributes.
- (NSArray<__kindof UICollectionViewLayoutAttributes*>*)
layoutAttributesForElementsInRect:(CGRect)rect {
NSArray* baseAttributes = [super layoutAttributesForElementsInRect:rect];
NSMutableArray<__kindof UICollectionViewLayoutAttributes*>* attributes =
[NSMutableArray array];
for (UICollectionViewLayoutAttributes* attribute in baseAttributes) {
UICollectionViewLayoutAttributes* newAttribute = [attribute copy];
newAttribute.alpha = kReorderingInactiveCellOpacity;
[attributes addObject:newAttribute];
}
return [attributes copy];
}
- (UICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath:
(NSIndexPath*)indexPath {
UICollectionViewLayoutAttributes* attributes =
[[super layoutAttributesForItemAtIndexPath:indexPath] copy];
attributes.alpha = kReorderingInactiveCellOpacity;
return attributes;
}
- (UICollectionViewLayoutAttributes*)
layoutAttributesForInteractivelyMovingItemAtIndexPath:(NSIndexPath*)indexPath
withTargetPosition:(CGPoint)position {
UICollectionViewLayoutAttributes* attributes = [[super
layoutAttributesForInteractivelyMovingItemAtIndexPath:indexPath
withTargetPosition:position] copy];
// The moving item has regular opacity, but is scaled.
attributes.alpha = 1.0;
attributes.transform =
CGAffineTransformScale(attributes.transform, kReorderingActiveCellScale,
kReorderingActiveCellScale);
return attributes;
}
@end