You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
512 lines
21 KiB
512 lines
21 KiB
/*****************************************************************************
|
|
* VLCLibraryCollectionViewFlowLayout.m: MacOS X interface module
|
|
*****************************************************************************
|
|
* Copyright (C) 2022 VLC authors and VideoLAN
|
|
*
|
|
* Authors: Claudio Cambra <claudio.cambra@gmail.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
|
|
*****************************************************************************/
|
|
|
|
#import "VLCLibraryCollectionViewFlowLayout.h"
|
|
|
|
#import "library/VLCLibraryCollectionViewDataSource.h"
|
|
#import "library/VLCLibraryCollectionViewMediaItemSupplementaryDetailView.h"
|
|
#import "library/VLCLibraryCollectionViewMediaItemListSupplementaryDetailView.h"
|
|
#import "library/VLCLibraryUIUnits.h"
|
|
|
|
#import "library/audio-library/VLCLibraryAudioDataSource.h"
|
|
#import "library/audio-library/VLCLibraryAudioGroupDataSource.h"
|
|
#import "library/audio-library/VLCLibraryCollectionViewAudioGroupSupplementaryDetailView.h"
|
|
|
|
#pragma mark - Private data
|
|
static const NSUInteger kAnimationSteps = 32;
|
|
static const NSUInteger kWrapAroundValue = (NSUInteger)-1;
|
|
|
|
static const CGFloat kDetailViewCollapsedHeight = 0.;
|
|
|
|
typedef NS_ENUM(NSUInteger, VLCDetailViewAnimationType)
|
|
{
|
|
VLCDetailViewAnimationTypeExpand,
|
|
VLCDetailViewAnimationTypeCollapse,
|
|
};
|
|
|
|
typedef NS_ENUM(NSUInteger, VLCExpandAnimationType) {
|
|
VLCExpandAnimationTypeVerticalMedium = 0,
|
|
VLCExpandAnimationTypeVerticalLarge,
|
|
VLCExpandAnimationTypeHorizontalMedium,
|
|
VLCExpandAnimationTypeHorizontalLarge,
|
|
};
|
|
|
|
static CVReturn detailViewAnimationCallback(CVDisplayLinkRef displayLink,
|
|
const CVTimeStamp *inNow,
|
|
const CVTimeStamp *inOutputTime,
|
|
CVOptionFlags flagsIn,
|
|
CVOptionFlags *flagsOut,
|
|
void *displayLinkContext);
|
|
|
|
#pragma mark - VLCLibraryCollectionViewFlowLayout
|
|
@interface VLCLibraryCollectionViewFlowLayout ()
|
|
{
|
|
CVDisplayLinkRef _displayLinkRef;
|
|
|
|
NSArray *_mediumHeightAnimationSteps;
|
|
NSArray *_largeHeightAnimationSteps;
|
|
NSArray *_mediumWidthAnimationSteps;
|
|
NSArray *_largeWidthAnimationSteps;
|
|
|
|
VLCExpandAnimationType _animationType;
|
|
CGFloat _prevProvidedAnimationStep;
|
|
|
|
BOOL _invalidateAll;
|
|
}
|
|
|
|
@property (nonatomic, readwrite) BOOL detailViewIsAnimating;
|
|
@property (nonatomic, readonly) BOOL animationIsCollapse;
|
|
@property (nonatomic, readwrite) NSUInteger animationIndex;
|
|
@property (nonatomic, readwrite, strong) NSIndexPath *selectedIndexPath;
|
|
|
|
@end
|
|
|
|
@implementation VLCLibraryCollectionViewFlowLayout
|
|
|
|
+ (instancetype)standardLayout
|
|
{
|
|
const CGFloat collectionItemSpacing = VLCLibraryUIUnits.collectionViewItemSpacing;
|
|
const NSEdgeInsets collectionViewSectionInset = VLCLibraryUIUnits.collectionViewSectionInsets;
|
|
|
|
VLCLibraryCollectionViewFlowLayout * const collectionViewLayout = [[VLCLibraryCollectionViewFlowLayout alloc] init];
|
|
collectionViewLayout.minimumLineSpacing = collectionItemSpacing;
|
|
collectionViewLayout.minimumInteritemSpacing = collectionItemSpacing;
|
|
collectionViewLayout.sectionInset = collectionViewSectionInset;
|
|
|
|
return collectionViewLayout;
|
|
}
|
|
|
|
- (instancetype)init
|
|
{
|
|
self = [super init];
|
|
if (self) {
|
|
_mediumHeightAnimationSteps = [self generateAnimationStepsForExpandedViewDimension:VLCLibraryUIUnits.mediumDetailSupplementaryViewCollectionViewHeight];
|
|
_largeHeightAnimationSteps = [self generateAnimationStepsForExpandedViewDimension:VLCLibraryUIUnits.largeDetailSupplementaryViewCollectionViewHeight];
|
|
_mediumWidthAnimationSteps = [self generateAnimationStepsForExpandedViewDimension:VLCLibraryUIUnits.mediumDetailSupplementaryViewCollectionViewWidth];
|
|
_largeWidthAnimationSteps = [self generateAnimationStepsForExpandedViewDimension:VLCLibraryUIUnits.largeDetailSupplementaryViewCollectionViewWidth];
|
|
|
|
_animationType = VLCExpandAnimationTypeVerticalMedium;
|
|
_prevProvidedAnimationStep = 0;
|
|
|
|
_invalidateAll = NO;
|
|
|
|
[self resetLayout];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (NSArray *)generateAnimationStepsForExpandedViewDimension:(NSInteger)dimension
|
|
{
|
|
NSMutableArray *generatedAnimationSteps = [NSMutableArray arrayWithCapacity:kAnimationSteps];
|
|
|
|
// Easing out cubic
|
|
for(NSUInteger i = 0; i < kAnimationSteps; ++i) {
|
|
CGFloat progress = (CGFloat)i / (CGFloat)kAnimationSteps;
|
|
progress -= 1;
|
|
generatedAnimationSteps[i] = @(dimension * (progress * progress * progress + 1) + kDetailViewCollapsedHeight);
|
|
}
|
|
|
|
return generatedAnimationSteps;
|
|
}
|
|
|
|
- (CGFloat)currentAnimationStep
|
|
{
|
|
if (_animationIndex < 0 || _animationIndex >= kAnimationSteps) {
|
|
return _prevProvidedAnimationStep; // Try to disguise problem
|
|
}
|
|
|
|
switch(_animationType) {
|
|
case VLCExpandAnimationTypeHorizontalMedium:
|
|
_prevProvidedAnimationStep = [_mediumWidthAnimationSteps[_animationIndex] floatValue];
|
|
break;
|
|
case VLCExpandAnimationTypeHorizontalLarge:
|
|
_prevProvidedAnimationStep = [_largeWidthAnimationSteps[_animationIndex] floatValue];
|
|
break;
|
|
case VLCExpandAnimationTypeVerticalLarge:
|
|
_prevProvidedAnimationStep = [_largeHeightAnimationSteps[_animationIndex] floatValue];
|
|
break;
|
|
case VLCExpandAnimationTypeVerticalMedium:
|
|
default:
|
|
_prevProvidedAnimationStep = [_mediumHeightAnimationSteps[_animationIndex] floatValue];
|
|
break;
|
|
}
|
|
|
|
return _prevProvidedAnimationStep;
|
|
}
|
|
|
|
- (CGFloat)finalExpandedHeight
|
|
{
|
|
switch(_animationType) {
|
|
case VLCExpandAnimationTypeHorizontalMedium:
|
|
return VLCLibraryUIUnits.mediumDetailSupplementaryViewCollectionViewWidth;
|
|
case VLCExpandAnimationTypeHorizontalLarge:
|
|
return VLCLibraryUIUnits.largeDetailSupplementaryViewCollectionViewWidth;
|
|
case VLCExpandAnimationTypeVerticalLarge:
|
|
return VLCLibraryUIUnits.largeDetailSupplementaryViewCollectionViewHeight;
|
|
case VLCExpandAnimationTypeVerticalMedium:
|
|
default:
|
|
return VLCLibraryUIUnits.mediumDetailSupplementaryViewCollectionViewHeight;
|
|
}
|
|
}
|
|
|
|
#pragma mark - Public methods
|
|
- (void)expandDetailSectionAtIndex:(NSIndexPath *)indexPath
|
|
{
|
|
if([_selectedIndexPath isEqual:indexPath]) {
|
|
return;
|
|
}
|
|
|
|
BOOL newItemOnSameRow = NO;
|
|
if (_selectedIndexPath != nil) {
|
|
NSCollectionViewItem * const oldSelectedItem = [self.collectionView itemAtIndexPath:_selectedIndexPath];
|
|
NSCollectionViewItem * const newSelectedItem = [self.collectionView itemAtIndexPath:indexPath];
|
|
|
|
newItemOnSameRow = oldSelectedItem.view.frame.origin.y == newSelectedItem.view.frame.origin.y;
|
|
}
|
|
|
|
_selectedIndexPath = indexPath;
|
|
|
|
if (!newItemOnSameRow) {
|
|
[self animateDetailViewWithAnimation:VLCDetailViewAnimationTypeExpand];
|
|
|
|
NSRect frame = [self.collectionView layoutAttributesForItemAtIndexPath:indexPath].frame;
|
|
frame.size.height += [self finalExpandedHeight] + VLCLibraryUIUnits.largeSpacing;
|
|
[self.collectionView.animator scrollRectToVisible:frame];
|
|
} else {
|
|
_animationIsCollapse = NO;
|
|
|
|
if ([self.collectionView.dataSource conformsToProtocol:@protocol(VLCLibraryCollectionViewDataSource)]) {
|
|
id<VLCLibraryCollectionViewDataSource> const libraryDataSource =
|
|
(id<VLCLibraryCollectionViewDataSource>)self.collectionView.dataSource;
|
|
NSString * const supplementaryDetailViewKind = libraryDataSource.supplementaryDetailViewKind;
|
|
if (supplementaryDetailViewKind != nil && supplementaryDetailViewKind.length > 0) {
|
|
NSSet<NSIndexPath *> * const visibleSupplementaryIndexPaths =
|
|
[self.collectionView indexPathsForVisibleSupplementaryElementsOfKind:supplementaryDetailViewKind];
|
|
|
|
for (NSIndexPath * const supplementaryIndexPath in visibleSupplementaryIndexPaths) {
|
|
VLCLibraryCollectionViewSupplementaryDetailView * const supplementaryView =
|
|
(VLCLibraryCollectionViewSupplementaryDetailView *)[self.collectionView supplementaryViewForElementKind:supplementaryDetailViewKind atIndexPath:supplementaryIndexPath];
|
|
if (supplementaryView != nil) {
|
|
supplementaryView.selectedItem = [self.collectionView itemAtIndexPath:indexPath];
|
|
supplementaryView.needsDisplay = YES;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)collapseDetailSectionAtIndex:(NSIndexPath *)indexPath
|
|
{
|
|
if(![_selectedIndexPath isEqual:indexPath]) {
|
|
return;
|
|
}
|
|
|
|
[self animateDetailViewWithAnimation:VLCDetailViewAnimationTypeCollapse];
|
|
}
|
|
|
|
- (void)resetLayout
|
|
{
|
|
[self releaseDisplayLink];
|
|
|
|
_selectedIndexPath = nil;
|
|
_detailViewIsAnimating = NO;
|
|
_animationIndex = 0;
|
|
|
|
[self invalidateLayout];
|
|
}
|
|
|
|
#pragma mark - Flow Layout methods
|
|
|
|
- (NSSize)collectionViewContentSize
|
|
{
|
|
NSSize contentSize = [super collectionViewContentSize];
|
|
|
|
if (!_selectedIndexPath) {
|
|
return contentSize;
|
|
}
|
|
|
|
if (self.scrollDirection == NSCollectionViewScrollDirectionVertical) {
|
|
contentSize.height += [self currentAnimationStep];
|
|
} else if (self.scrollDirection == NSCollectionViewScrollDirectionHorizontal) {
|
|
contentSize.width += [self currentAnimationStep];
|
|
}
|
|
return contentSize;
|
|
}
|
|
|
|
- (BOOL)shouldInvalidateLayoutForBoundsChange:(NSRect)newBounds
|
|
{
|
|
[super shouldInvalidateLayoutForBoundsChange:newBounds];
|
|
_invalidateAll = YES;
|
|
return YES;
|
|
}
|
|
|
|
- (void)invalidateLayoutWithContext:(NSCollectionViewLayoutInvalidationContext *)context
|
|
{
|
|
NSCollectionViewFlowLayoutInvalidationContext *flowLayoutContext = (NSCollectionViewFlowLayoutInvalidationContext *)context;
|
|
if (flowLayoutContext && _invalidateAll) {
|
|
flowLayoutContext.invalidateFlowLayoutAttributes = YES;
|
|
flowLayoutContext.invalidateFlowLayoutDelegateMetrics = YES;
|
|
_invalidateAll = NO;
|
|
}
|
|
|
|
[super invalidateLayoutWithContext:context];
|
|
}
|
|
|
|
- (NSCollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
if(_selectedIndexPath == nil || indexPath == _selectedIndexPath) {
|
|
return [super layoutAttributesForItemAtIndexPath:indexPath];
|
|
}
|
|
|
|
NSCollectionViewLayoutAttributes * const attributes =
|
|
[super layoutAttributesForItemAtIndexPath:indexPath].copy;
|
|
[attributes setFrame:[self frameForDisplacedAttributes:attributes]];
|
|
return attributes;
|
|
}
|
|
|
|
- (NSArray<__kindof NSCollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(NSRect)rect
|
|
{
|
|
if (_selectedIndexPath == nil) {
|
|
return [super layoutAttributesForElementsInRect:rect];
|
|
}
|
|
|
|
// Computed attributes from parent
|
|
NSMutableArray<__kindof NSCollectionViewLayoutAttributes *> * const layoutAttributesArray =
|
|
[super layoutAttributesForElementsInRect:rect].mutableCopy;
|
|
for (NSUInteger i = 0; i < layoutAttributesArray.count; i++) {
|
|
NSCollectionViewLayoutAttributes * const attributes = layoutAttributesArray[i].copy;
|
|
NSString * const elementKind = attributes.representedElementKind;
|
|
|
|
if (@available(macOS 10.12, *)) {
|
|
if (([elementKind isEqualToString:NSCollectionElementKindSectionHeader] && self.sectionHeadersPinToVisibleBounds) ||
|
|
([elementKind isEqualToString:NSCollectionElementKindSectionFooter] && self.sectionFootersPinToVisibleBounds)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
[attributes setFrame:[self frameForDisplacedAttributes:attributes]];
|
|
layoutAttributesArray[i] = attributes;
|
|
}
|
|
|
|
if ([self.collectionView.dataSource conformsToProtocol:@protocol(VLCLibraryCollectionViewDataSource)]) {
|
|
id<VLCLibraryCollectionViewDataSource> const libraryDataSource =
|
|
(id<VLCLibraryCollectionViewDataSource>)self.collectionView.dataSource;
|
|
NSString * const supplementaryDetailViewKind = libraryDataSource.supplementaryDetailViewKind;
|
|
if (supplementaryDetailViewKind != nil && supplementaryDetailViewKind.length > 0) {
|
|
NSCollectionViewLayoutAttributes * const supplementaryDetailViewLayoutAttributes =
|
|
[self layoutAttributesForSupplementaryViewOfKind:supplementaryDetailViewKind
|
|
atIndexPath:self.selectedIndexPath];
|
|
[layoutAttributesArray addObject:supplementaryDetailViewLayoutAttributes];
|
|
}
|
|
}
|
|
|
|
return layoutAttributesArray;
|
|
}
|
|
|
|
- (NSCollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSCollectionViewSupplementaryElementKind)elementKind
|
|
atIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
BOOL isLibrarySupplementaryView = NO;
|
|
|
|
if ([elementKind isEqualToString:VLCLibraryCollectionViewMediaItemListSupplementaryDetailViewKind] ||
|
|
[elementKind isEqualToString:VLCLibraryCollectionViewMediaItemSupplementaryDetailViewKind]) {
|
|
|
|
isLibrarySupplementaryView = YES;
|
|
_animationType = self.scrollDirection == NSCollectionViewScrollDirectionVertical ? VLCExpandAnimationTypeVerticalMedium : VLCExpandAnimationTypeHorizontalMedium;
|
|
} else if ([elementKind isEqualToString:VLCLibraryCollectionViewAudioGroupSupplementaryDetailViewKind]) {
|
|
|
|
isLibrarySupplementaryView = YES;
|
|
_animationType = self.scrollDirection == NSCollectionViewScrollDirectionVertical ? VLCExpandAnimationTypeVerticalLarge : VLCExpandAnimationTypeHorizontalLarge;
|
|
}
|
|
|
|
if(isLibrarySupplementaryView) {
|
|
NSCollectionViewLayoutAttributes *detailViewAttributes = [NSCollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:elementKind
|
|
withIndexPath:indexPath];
|
|
NSAssert1(detailViewAttributes != NULL,
|
|
@"Failed to create NSCollectionViewLayoutAttributes for view of kind %@.",
|
|
elementKind);
|
|
|
|
const NSRect selectedItemFrame = [[self layoutAttributesForItemAtIndexPath:_selectedIndexPath] frame];
|
|
|
|
if (self.scrollDirection == NSCollectionViewScrollDirectionVertical) {
|
|
const float selectedItemFrameMaxY = _selectedIndexPath == nil ? 0 : NSMaxY(selectedItemFrame);
|
|
detailViewAttributes.frame = NSMakeRect(NSMinX(self.collectionView.frame) + self.minimumInteritemSpacing,
|
|
selectedItemFrameMaxY + VLCLibraryUIUnits.mediumSpacing,
|
|
self.collectionViewContentSize.width - (self.minimumInteritemSpacing * 2),
|
|
[self currentAnimationStep]);
|
|
|
|
} else if (self.scrollDirection == NSCollectionViewScrollDirectionHorizontal) {
|
|
const float selectedItemFrameMinY = _selectedIndexPath == nil ? 0 : NSMinY(selectedItemFrame);
|
|
const float selectedItemFrameMaxX = _selectedIndexPath == nil ? 0 : NSMaxX(selectedItemFrame);
|
|
const float selectedItemFrameHeight = _selectedIndexPath == nil ? 0 : selectedItemFrame.size.height;
|
|
detailViewAttributes.frame = NSMakeRect(selectedItemFrameMaxX + self.minimumInteritemSpacing,
|
|
selectedItemFrameMinY,
|
|
[self currentAnimationStep],
|
|
selectedItemFrameHeight);
|
|
}
|
|
|
|
return detailViewAttributes;
|
|
}
|
|
|
|
// Default attributes
|
|
NSCollectionViewLayoutAttributes * const attributes =
|
|
[super layoutAttributesForSupplementaryViewOfKind:elementKind atIndexPath:indexPath].copy;
|
|
[attributes setFrame:[self frameForDisplacedAttributes:attributes]];
|
|
return attributes;
|
|
}
|
|
|
|
- (NSSet<NSIndexPath *> *)indexPathsToDeleteForSupplementaryViewOfKind:(NSString *)elementKind
|
|
{
|
|
if ([elementKind isEqualToString:VLCLibraryCollectionViewMediaItemListSupplementaryDetailViewKind] ||
|
|
[elementKind isEqualToString:VLCLibraryCollectionViewMediaItemSupplementaryDetailViewKind] ||
|
|
[elementKind isEqualToString:VLCLibraryCollectionViewAudioGroupSupplementaryDetailViewKind]) {
|
|
|
|
return [self.collectionView indexPathsForVisibleSupplementaryElementsOfKind:elementKind];
|
|
}
|
|
return [NSSet set];
|
|
}
|
|
|
|
# pragma mark - Calculation of displaced frame attributes
|
|
|
|
- (NSRect)frameForDisplacedAttributes:(NSCollectionViewLayoutAttributes *)inAttributes
|
|
{
|
|
if(inAttributes == nil) {
|
|
return NSZeroRect;
|
|
}
|
|
|
|
NSRect attributesFrame = inAttributes.frame;
|
|
|
|
if (self.selectedIndexPath) {
|
|
NSCollectionViewLayoutAttributes *selectedItemLayoutAttributes = [self layoutAttributesForItemAtIndexPath:_selectedIndexPath];
|
|
|
|
if(selectedItemLayoutAttributes == nil) {
|
|
return attributesFrame;
|
|
}
|
|
|
|
NSRect selectedItemFrame = selectedItemLayoutAttributes.frame;
|
|
|
|
if (self.scrollDirection == NSCollectionViewScrollDirectionVertical &&
|
|
NSMinY(attributesFrame) > (NSMaxY(selectedItemFrame))) {
|
|
|
|
attributesFrame.origin.y += [self currentAnimationStep] + VLCLibraryUIUnits.mediumSpacing;
|
|
|
|
} else if (self.scrollDirection == NSCollectionViewScrollDirectionHorizontal &&
|
|
NSMinX(attributesFrame) > (NSMaxX(selectedItemFrame))) {
|
|
|
|
attributesFrame.origin.y += [self currentAnimationStep] + VLCLibraryUIUnits.mediumSpacing;
|
|
attributesFrame.origin.x += [self currentAnimationStep] + VLCLibraryUIUnits.mediumSpacing;
|
|
}
|
|
}
|
|
|
|
return attributesFrame;
|
|
}
|
|
|
|
#pragma mark - Detail view animation
|
|
- (void)animateDetailViewWithAnimation:(VLCDetailViewAnimationType)type
|
|
{
|
|
if (type == VLCDetailViewAnimationTypeExpand) {
|
|
_animationIsCollapse = NO;
|
|
_animationIndex = kWrapAroundValue;
|
|
} else {
|
|
_animationIsCollapse = YES;
|
|
_animationIndex = kAnimationSteps;
|
|
}
|
|
|
|
_detailViewIsAnimating = YES;
|
|
|
|
if (_displayLinkRef == NULL) {
|
|
[self initDisplayLink];
|
|
}
|
|
}
|
|
|
|
- (void)initDisplayLink
|
|
{
|
|
const CVReturn createResult = CVDisplayLinkCreateWithActiveCGDisplays(&_displayLinkRef);
|
|
|
|
if ((createResult != kCVReturnSuccess) || (_displayLinkRef == NULL)) {
|
|
_detailViewIsAnimating = NO;
|
|
return;
|
|
}
|
|
|
|
CVDisplayLinkSetOutputCallback(_displayLinkRef, detailViewAnimationCallback, (__bridge void *)self);
|
|
CVDisplayLinkStart(_displayLinkRef);
|
|
}
|
|
|
|
- (void)releaseDisplayLink
|
|
{
|
|
if (_displayLinkRef == NULL ) {
|
|
return;
|
|
}
|
|
|
|
CVDisplayLinkStop(_displayLinkRef);
|
|
CVDisplayLinkRelease(_displayLinkRef);
|
|
|
|
_displayLinkRef = NULL;
|
|
}
|
|
|
|
@end
|
|
|
|
static CVReturn detailViewAnimationCallback(
|
|
CVDisplayLinkRef displayLink __unused,
|
|
const CVTimeStamp *inNow __unused,
|
|
const CVTimeStamp *inOutputTime __unused,
|
|
CVOptionFlags flagsIn __unused,
|
|
CVOptionFlags *flagsOut __unused,
|
|
void *displayLinkContext)
|
|
{
|
|
if (displayLinkContext == nil) {
|
|
return kCVReturnError;
|
|
}
|
|
|
|
VLCLibraryCollectionViewFlowLayout *bridgedSelf = (__bridge VLCLibraryCollectionViewFlowLayout *)displayLinkContext;
|
|
BOOL animationFinished = NO;
|
|
|
|
if(bridgedSelf.detailViewIsAnimating) {
|
|
if (bridgedSelf.animationIsCollapse) {
|
|
--bridgedSelf.animationIndex;
|
|
animationFinished = (bridgedSelf.animationIndex == kWrapAroundValue);
|
|
} else {
|
|
++bridgedSelf.animationIndex;
|
|
animationFinished = (bridgedSelf.animationIndex == kAnimationSteps);
|
|
}
|
|
}
|
|
|
|
if (bridgedSelf.detailViewIsAnimating == NO || animationFinished) {
|
|
bridgedSelf.detailViewIsAnimating = NO;
|
|
[bridgedSelf releaseDisplayLink];
|
|
|
|
if (bridgedSelf.animationIsCollapse) {
|
|
bridgedSelf.selectedIndexPath = nil;
|
|
bridgedSelf.animationIndex = 0;
|
|
} else {
|
|
bridgedSelf.animationIndex = kAnimationSteps - 1;
|
|
}
|
|
}
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^(void){
|
|
[bridgedSelf invalidateLayout];
|
|
});
|
|
|
|
return kCVReturnSuccess;
|
|
}
|
|
|