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.
 
 
 
 
 
 

279 lines
9.3 KiB

/*****************************************************************************
* VLCLibraryHomeViewBaseCarouselContainerView.m: MacOS X interface module
*****************************************************************************
* Copyright (C) 2024 VLC authors and VideoLAN
*
* Authors: Claudio Cambra <developer@claudiocambra.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 "VLCLibraryHomeViewBaseCarouselContainerView.h"
#import "extensions/NSFont+VLCAdditions.h"
#import "extensions/NSString+Helpers.h"
#import "library/VLCLibraryDataTypes.h"
#import "library/VLCLibraryCarouselViewItemView.h"
#import "library/VLCLibraryUIUnits.h"
@interface VLCLibraryHomeViewBaseCarouselContainerView ()
{
CGFloat _itemHeight;
}
@property (readonly) NSLayoutConstraint *heightConstraint;
@property (readwrite) VLCLibraryCarouselViewItemView *selectedItemView;
@end
@implementation VLCLibraryHomeViewBaseCarouselContainerView
@synthesize constraintsWithSuperview = _constraintsWithSuperview;
- (instancetype)init
{
self = [super init];
if (self) {
[self setup];
}
return self;
}
- (instancetype)initWithFrame:(NSRect)frameRect
{
self = [super initWithFrame:frameRect];
if (self) {
[self setup];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self setup];
}
return self;
}
- (void)awakeFromNib
{
[super awakeFromNib];
[self setup];
}
- (void)setup
{
[self setupView];
[self connect];
}
- (void)setupView
{
_titleView = [[NSTextField alloc] init];
self.titleView.font = NSFont.VLClibrarySectionHeaderFont;
self.titleView.textColor = NSColor.headerTextColor;
self.titleView.selectable = NO;
self.titleView.bordered = NO;
self.titleView.drawsBackground = NO;
[self addSubview:self.titleView];
self.titleView.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[self.leadingAnchor constraintEqualToAnchor:self.titleView.leadingAnchor],
[self.trailingAnchor constraintEqualToAnchor:self.titleView.trailingAnchor],
[self.topAnchor constraintEqualToAnchor:self.titleView.topAnchor],
]];
_carouselView = [[iCarousel alloc] initWithFrame:self.bounds];
self.carouselView.delegate = self;
[self addSubview:self.carouselView];
self.carouselView.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[self.leadingAnchor constraintEqualToAnchor:self.carouselView.leadingAnchor],
[self.trailingAnchor constraintEqualToAnchor:self.carouselView.trailingAnchor],
[self.titleView.bottomAnchor constraintEqualToAnchor:self.carouselView.topAnchor], // titleView bottom
[self.bottomAnchor constraintEqualToAnchor:self.carouselView.bottomAnchor]
]];
const CGFloat buttonWidth = VLCLibraryUIUnits.largeSpacing;
const CGFloat buttonHeight =
VLCLibraryUIUnits.carouselViewItemViewHeight - VLCLibraryUIUnits.largeSpacing;
NSImage * const leftImage = [NSImage imageNamed:@"NSGoLeftTemplate"];
_leftButton = [[NSButton alloc] initWithFrame:NSZeroRect];
self.leftButton.translatesAutoresizingMaskIntoConstraints = NO;
self.leftButton.image = leftImage;
self.leftButton.bezelStyle = NSBezelStyleFlexiblePush;
self.leftButton.target = self;
self.leftButton.action = @selector(scrollLeft:);
[self addSubview:self.leftButton];
[NSLayoutConstraint activateConstraints:@[
[self.leftButton.leadingAnchor constraintEqualToAnchor:self.leadingAnchor],
[self.leftButton.centerYAnchor constraintEqualToAnchor:self.carouselView.centerYAnchor],
[self.leftButton.heightAnchor constraintEqualToConstant:buttonHeight],
[self.leftButton.widthAnchor constraintEqualToConstant:buttonWidth]
]];
NSImage * const rightImage = [NSImage imageNamed:@"NSGoRightTemplate"];
_rightButton = [[NSButton alloc] initWithFrame:NSZeroRect];
self.rightButton.translatesAutoresizingMaskIntoConstraints = NO;
self.rightButton.image = rightImage;
self.rightButton.bezelStyle = NSBezelStyleFlexiblePush;
self.rightButton.target = self;
self.rightButton.action = @selector(scrollRight:);
[self addSubview:self.rightButton];
[NSLayoutConstraint activateConstraints:@[
[self.rightButton.trailingAnchor constraintEqualToAnchor:self.carouselView.trailingAnchor],
[self.rightButton.centerYAnchor constraintEqualToAnchor:self.carouselView.centerYAnchor],
[self.rightButton.heightAnchor constraintEqualToConstant:buttonHeight],
[self.rightButton.widthAnchor constraintEqualToConstant:buttonWidth]
]];
_itemHeight = VLCLibraryUIUnits.carouselViewItemViewHeight;
[self updateCarouselViewHeight];
[self updateCarouselOffset];
}
- (void)connect
{
[self.dataSource connect];
}
- (void)disconnect
{
[self.dataSource disconnect];
}
- (void)updateCarouselViewHeight
{
const CGFloat viewHeight = self.titleView.frame.size.height +
VLCLibraryUIUnits.largeSpacing * 2 +
_itemHeight;
if (self.heightConstraint == nil) {
_heightConstraint = [self.carouselView.heightAnchor constraintEqualToConstant:viewHeight];
self.heightConstraint.active = YES;
} else {
self.heightConstraint.constant = viewHeight;
}
}
- (void)updateCarouselOffset
{
const CGFloat widthToFirstItemCenter = self.frame.size.width / 2;
const CGFloat leadingPadding = VLCLibraryUIUnits.largeSpacing + VLCLibraryUIUnits.mediumSpacing;
const CGFloat itemWidth = self.carouselView.itemWidth;
const CGFloat horizontalOffset = (-(widthToFirstItemCenter - itemWidth / 2)) + leadingPadding;
self.carouselView.contentOffset = NSMakeSize(horizontalOffset, 0);
}
- (void)resizeWithOldSuperviewSize:(NSSize)oldSize
{
[super resizeWithOldSuperviewSize:oldSize];
[self updateCarouselOffset];
}
- (void)presentLibraryItem:(id<VLCMediaLibraryItemProtocol>)libraryItem
{
if (libraryItem == nil) {
return;
}
NSIndexPath * const itemIndexPath = [self.dataSource indexPathForLibraryItem:libraryItem];
if (itemIndexPath == nil) {
return;
}
const NSInteger itemIndex = itemIndexPath.item;
[self.carouselView scrollToItemAtIndex:itemIndex animated:YES];
}
- (CGFloat)itemHeight
{
return _itemHeight;
}
- (void)setItemHeight:(CGFloat)itemHeight
{
if (itemHeight == self.itemHeight) {
return;
}
_itemHeight = itemHeight;
[self updateCarouselViewHeight];
}
- (void)scrollLeft:(id)sender
{
[self.carouselView scrollToItemAtIndex:self.carouselView.currentItemIndex - 1 animated:YES];
}
- (void)scrollRight:(id)sender
{
[self.carouselView scrollToItemAtIndex:self.carouselView.currentItemIndex + 1 animated:YES];
}
// pragma mark - iCarousel delegate methods
- (CGFloat)carousel:(iCarousel *)carousel
valueForOption:(iCarouselOption)option
withDefault:(CGFloat)value
{
switch (option) {
case iCarouselOptionSpacing:
{
// iCarousel calculates spacing as a multiplier on the item width.
// So a spacing of 1.2 means the item's width will grow to 1.2x its
// width, with the extra width as spacing.
//
// Because of this... interesting approach to spacing, we calculate
// the constant VLC-wide spacing relative to the width of the carousel's
// itemWidth.
const CGFloat itemWidth = carousel.itemWidth;
const CGFloat bothSidesSpacing = VLCLibraryUIUnits.mediumSpacing * 2;
const CGFloat desiredWidthWithSpacing = itemWidth + bothSidesSpacing;
const CGFloat desiredMultiple = desiredWidthWithSpacing / itemWidth;
return desiredMultiple;
}
default:
return value;
}
}
- (void)carouselCurrentItemIndexDidChange:(iCarousel *)carousel
{
NSView * const currentItemView = carousel.currentItemView;
if (currentItemView == nil) {
return;
}
VLCLibraryCarouselViewItemView * const carouselItemView = (VLCLibraryCarouselViewItemView *)currentItemView;
NSAssert(carouselItemView != nil, @"Expected carousel item view to be non-nil!");
self.selectedItemView.selected = NO;
carouselItemView.selected = YES;
self.selectedItemView = carouselItemView;
}
- (void)carousel:(iCarousel *)carousel didSelectItemAtIndex:(NSInteger)index
{
VLCLibraryCarouselViewItemView * const carouselItemView = (VLCLibraryCarouselViewItemView *)[carousel itemViewAtIndex:index];
NSAssert(carouselItemView != nil, @"Expected carousel item view to be non-nil!");
[carouselItemView playRepresentedItem];
}
@end