macosx: Add expandable detail view to the album collection view

Signed-off-by: Claudio Cambra <claudio.cambra@gmail.com>
This commit is contained in:
Claudio Cambra 2022-07-15 20:35:22 +02:00
parent fa106a29ad
commit 53c0bfa28f
12 changed files with 799 additions and 82 deletions

View File

@ -58,10 +58,16 @@ libmacosx_plugin_la_SOURCES = \
gui/macosx/library/VLCInputItem.m \
gui/macosx/library/VLCLibraryAlbumTableCellView.h \
gui/macosx/library/VLCLibraryAlbumTableCellView.m \
gui/macosx/library/VLCLibraryAlbumTracksDataSource.h \
gui/macosx/library/VLCLibraryAlbumTracksDataSource.m \
gui/macosx/library/VLCLibraryAudioDataSource.h \
gui/macosx/library/VLCLibraryAudioDataSource.m \
gui/macosx/library/VLCLibraryCollectionViewFlowLayout.h \
gui/macosx/library/VLCLibraryCollectionViewFlowLayout.m \
gui/macosx/library/VLCLibraryCollectionViewItem.h \
gui/macosx/library/VLCLibraryCollectionViewItem.m \
gui/macosx/library/VLCLibraryCollectionViewAlbumSupplementaryDetailView.h \
gui/macosx/library/VLCLibraryCollectionViewAlbumSupplementaryDetailView.m \
gui/macosx/library/VLCLibraryCollectionViewSupplementaryElementView.h \
gui/macosx/library/VLCLibraryCollectionViewSupplementaryElementView.m \
gui/macosx/library/VLCLibraryController.h \
@ -297,6 +303,7 @@ libmacosx_plugin_la_XIB_SOURCES = \
gui/macosx/UI/VLCLibraryTableCellView.xib \
gui/macosx/UI/VLCPlaylistTableCellView.xib \
gui/macosx/UI/VLCLibraryCollectionViewItem.xib \
gui/macosx/UI/VLCLibraryCollectionViewAlbumSupplementaryDetailView.xib \
gui/macosx/UI/VLCMediaSourceCollectionViewItem.xib \
gui/macosx/UI/VLCMediaSourceDeviceCollectionViewItem.xib \
gui/macosx/UI/VLCInformationWindow.xib \

View File

@ -0,0 +1,164 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
<capability name="System colors introduced in macOS 10.14" minToolsVersion="10.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="VLCLibraryCollectionViewAlbumItem"/>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<view id="HAc-or-XD8" customClass="VLCLibraryCollectionViewAlbumSupplementaryDetailView">
<rect key="frame" x="0.0" y="0.0" width="1061" height="332"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<subviews>
<box boxType="custom" borderType="none" cornerRadius="4" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="TDx-ys-0hc">
<rect key="frame" x="0.0" y="0.0" width="1061" height="312"/>
<view key="contentView" id="l4M-qd-E0u">
<rect key="frame" x="0.0" y="0.0" width="1061" height="312"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</view>
<color key="borderColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<color key="fillColor" name="separatorColor" catalog="System" colorSpace="catalog"/>
</box>
<stackView distribution="fill" orientation="horizontal" alignment="top" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="FWp-yd-2Pm">
<rect key="frame" x="0.0" y="0.0" width="1061" height="302"/>
<subviews>
<stackView distribution="fill" orientation="vertical" alignment="leading" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="TOx-1e-6D9">
<rect key="frame" x="0.0" y="10" width="351" height="292"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="xZd-Hk-h2M">
<rect key="frame" x="10" y="0.0" width="331" height="292"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="no-art" id="6X6-Og-u8A"/>
</imageView>
</subviews>
<constraints>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="xZd-Hk-h2M" secondAttribute="trailing" constant="10" id="GHL-NF-uZf"/>
<constraint firstItem="xZd-Hk-h2M" firstAttribute="top" secondItem="TOx-1e-6D9" secondAttribute="top" id="fYG-48-y9a"/>
<constraint firstItem="xZd-Hk-h2M" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="TOx-1e-6D9" secondAttribute="leading" constant="10" id="gvA-cd-m3X"/>
<constraint firstItem="xZd-Hk-h2M" firstAttribute="centerX" secondItem="TOx-1e-6D9" secondAttribute="centerX" id="zLM-Ut-V4H"/>
</constraints>
<visibilityPriorities>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
<stackView distribution="fill" orientation="vertical" alignment="leading" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bw7-QB-Ssc">
<rect key="frame" x="359" y="20" width="702" height="282"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nCe-dY-YMM">
<rect key="frame" x="-2" y="230" width="124" height="52"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Album name" id="6RM-x8-Y4y">
<font key="font" textStyle="title1" name=".SFNS-Regular"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="751" translatesAutoresizingMaskIntoConstraints="NO" id="HnP-Fk-juB">
<rect key="frame" x="-2" y="206" width="87" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Album details" id="Nwk-76-Wx9">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="52" horizontalPageScroll="10" verticalLineScroll="52" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" horizontalScrollElasticity="none" id="9ZS-oy-iP9">
<rect key="frame" x="0.0" y="0.0" width="701" height="198"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES"/>
<clipView key="contentView" drawsBackground="NO" id="3V4-tX-owM">
<rect key="frame" x="0.0" y="0.0" width="701" height="198"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" multipleSelection="NO" autosaveColumns="NO" rowHeight="50" viewBased="YES" id="eEJ-WA-0aM">
<rect key="frame" x="0.0" y="0.0" width="701" height="198"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<tableViewGridLines key="gridStyleMask" horizontal="YES"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns>
<tableColumn editable="NO" width="641" minWidth="10" maxWidth="1000" id="tVn-dP-rPg">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="LqL-cf-UYG">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES"/>
<prototypeCellViews>
<tableCellView id="vmz-MH-Uum">
<rect key="frame" x="11" y="1" width="650" height="24"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</tableCellView>
</prototypeCellViews>
</tableColumn>
</tableColumns>
</tableView>
</subviews>
<nil key="backgroundColor"/>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="ZBY-pE-E7T">
<rect key="frame" x="-100" y="-100" width="701" height="15"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="WRE-VM-L7s">
<rect key="frame" x="224" y="17" width="15" height="102"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
</subviews>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="TOx-1e-6D9" secondAttribute="bottom" constant="10" id="sVI-op-597"/>
<constraint firstItem="TOx-1e-6D9" firstAttribute="width" secondItem="bw7-QB-Ssc" secondAttribute="width" multiplier="0.5" id="wgl-yE-5rb"/>
</constraints>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
</subviews>
<constraints>
<constraint firstItem="TDx-ys-0hc" firstAttribute="trailing" secondItem="FWp-yd-2Pm" secondAttribute="trailing" id="288-Qz-cb7"/>
<constraint firstItem="TDx-ys-0hc" firstAttribute="leading" secondItem="FWp-yd-2Pm" secondAttribute="leading" id="4AV-xO-cVg"/>
<constraint firstItem="FWp-yd-2Pm" firstAttribute="leading" secondItem="HAc-or-XD8" secondAttribute="leading" id="D62-3E-aDO"/>
<constraint firstItem="TDx-ys-0hc" firstAttribute="top" secondItem="HAc-or-XD8" secondAttribute="top" constant="20" id="Wob-l9-MuU"/>
<constraint firstAttribute="bottom" secondItem="TDx-ys-0hc" secondAttribute="bottom" id="aub-cX-QrF"/>
<constraint firstAttribute="trailing" secondItem="FWp-yd-2Pm" secondAttribute="trailing" id="cPm-6m-umY"/>
<constraint firstItem="FWp-yd-2Pm" firstAttribute="top" secondItem="TDx-ys-0hc" secondAttribute="top" constant="10" id="cVs-ac-sSk"/>
<constraint firstItem="FWp-yd-2Pm" firstAttribute="bottom" secondItem="TDx-ys-0hc" secondAttribute="bottom" id="pZF-BE-cG1"/>
</constraints>
<connections>
<outlet property="albumArtworkImageView" destination="xZd-Hk-h2M" id="J8l-V9-P06"/>
<outlet property="albumDetailsTextField" destination="HnP-Fk-juB" id="Hm3-l8-a9s"/>
<outlet property="albumTitleTextField" destination="nCe-dY-YMM" id="h3l-p0-w3e"/>
<outlet property="albumTracksTableView" destination="eEJ-WA-0aM" id="l8k-M9-a8e"/>
</connections>
<point key="canvasLocation" x="-237.5" y="-257"/>
</view>
</objects>
<resources>
<image name="no-art" width="128" height="128"/>
</resources>
</document>

View File

@ -25,12 +25,13 @@
NS_ASSUME_NONNULL_BEGIN
@class VLCImageView;
@class VLCLibraryTracksDataSource;
@class VLCTrackingView;
@class VLCMediaLibraryAlbum;
@interface VLCLibraryAlbumTableCellView : NSTableCellView
extern NSString *VLCAudioLibraryCellIdentifier;
+ (CGFloat)defaultHeight;
+ (CGFloat)heightForAlbum:(VLCMediaLibraryAlbum *)album;

View File

@ -29,22 +29,17 @@
#import "library/VLCLibraryController.h"
#import "library/VLCLibraryDataTypes.h"
#import "library/VLCLibraryTableCellView.h"
#import "library/VLCLibraryAlbumTracksDataSource.h"
NSString *VLCAudioLibraryCellIdentifier = @"VLCAudioLibraryCellIdentifier";
const CGFloat VLCLibraryTracksRowHeight = 50.;
const CGFloat VLCLibraryAlbumTableCellViewDefaultHeight = 168.;
const CGFloat LayoutSpacer;
@interface VLCLibraryTracksDataSource : NSObject <NSTableViewDataSource, NSTableViewDelegate>
@property (readwrite, retain, nonatomic, nullable) VLCMediaLibraryAlbum *representedAlbum;
@end
@interface VLCLibraryAlbumTableCellView ()
{
VLCLibraryController *_libraryController;
VLCLibraryTracksDataSource *_tracksDataSource;
VLCLibraryAlbumTracksDataSource *_tracksDataSource;
NSTableView *_tracksTableView;
}
@end
@ -75,7 +70,7 @@ const CGFloat LayoutSpacer;
_tracksTableView.rowHeight = VLCLibraryTracksRowHeight;
[_tracksTableView addTableColumn:column];
_tracksTableView.translatesAutoresizingMaskIntoConstraints = NO;
_tracksDataSource = [[VLCLibraryTracksDataSource alloc] init];
_tracksDataSource = [[VLCLibraryAlbumTracksDataSource alloc] init];
_tracksTableView.dataSource = _tracksDataSource;
_tracksTableView.delegate = _tracksDataSource;
_tracksTableView.doubleAction = @selector(tracksTableViewDoubleClickAction:);
@ -152,68 +147,3 @@ const CGFloat LayoutSpacer;
}
@end
@interface VLCLibraryTracksDataSource ()
{
NSArray *_tracks;
}
@end
@implementation VLCLibraryTracksDataSource
- (void)setRepresentedAlbum:(VLCMediaLibraryAlbum *)representedAlbum
{
_representedAlbum = representedAlbum;
_tracks = [_representedAlbum tracksAsMediaItems];
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
if (_representedAlbum != nil) {
return _representedAlbum.numberOfTracks;
}
return 0;
}
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
VLCLibraryTableCellView *cellView = [tableView makeViewWithIdentifier:VLCAudioLibraryCellIdentifier owner:self];
if (cellView == nil) {
/* the following code saves us an instance of NSViewController which we don't need */
NSNib *nib = [[NSNib alloc] initWithNibNamed:@"VLCLibraryTableCellView" bundle:nil];
NSArray *topLevelObjects;
if (![nib instantiateWithOwner:self topLevelObjects:&topLevelObjects]) {
NSAssert(1, @"Failed to load nib file to show audio library items");
return nil;
}
for (id topLevelObject in topLevelObjects) {
if ([topLevelObject isKindOfClass:[VLCLibraryTableCellView class]]) {
cellView = topLevelObject;
break;
}
}
cellView.identifier = VLCAudioLibraryCellIdentifier;
}
VLCMediaLibraryMediaItem *mediaItem = _tracks[row];
NSImage *image = mediaItem.smallArtworkImage;
if (!image) {
image = [NSImage imageNamed: @"noart.png"];
}
cellView.representedImageView.image = image;
cellView.representedMediaItem = mediaItem;
NSString *title = mediaItem.title;
cellView.primaryTitleTextField.hidden = NO;
cellView.secondaryTitleTextField.hidden = NO;
cellView.primaryTitleTextField.stringValue = title;
cellView.secondaryTitleTextField.stringValue = [NSString stringWithTime:mediaItem.duration / 1000];
return cellView;
}
@end

View File

@ -0,0 +1,35 @@
/*****************************************************************************
* VLCLibraryAlbumTracksDataSource.h: 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 <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
@class VLCMediaLibraryAlbum;
@interface VLCLibraryAlbumTracksDataSource : NSObject <NSTableViewDataSource, NSTableViewDelegate>
@property (readwrite, retain, nonatomic, nullable) VLCMediaLibraryAlbum *representedAlbum;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,98 @@
/*****************************************************************************
* VLCLibraryAlbumTracksDataSource.m: MacOS X interface module
*****************************************************************************
* Copyright (C) 2019 VLC authors and VideoLAN
*
* Authors: Felix Paul Kühne <fkuehne # videolan -dot- org>
*
* 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 "VLCLibraryAlbumTracksDataSource.h"
#import "VLCLibraryAlbumTableCellView.h"
#import "extensions/NSFont+VLCAdditions.h"
#import "extensions/NSString+Helpers.h"
#import "views/VLCImageView.h"
#import "views/VLCTrackingView.h"
#import "main/VLCMain.h"
#import "library/VLCLibraryController.h"
#import "library/VLCLibraryDataTypes.h"
#import "library/VLCLibraryTableCellView.h"
#import "library/VLCLibraryAlbumTracksDataSource.h"
@interface VLCLibraryAlbumTracksDataSource ()
{
NSArray *_tracks;
}
@end
@implementation VLCLibraryAlbumTracksDataSource
- (void)setRepresentedAlbum:(VLCMediaLibraryAlbum *)representedAlbum
{
_representedAlbum = representedAlbum;
_tracks = [_representedAlbum tracksAsMediaItems];
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
if (_representedAlbum != nil) {
return _representedAlbum.numberOfTracks;
}
return 0;
}
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
VLCLibraryTableCellView *cellView = [tableView makeViewWithIdentifier:VLCAudioLibraryCellIdentifier owner:self];
if (cellView == nil) {
/* the following code saves us an instance of NSViewController which we don't need */
NSNib *nib = [[NSNib alloc] initWithNibNamed:@"VLCLibraryTableCellView" bundle:nil];
NSArray *topLevelObjects;
if (![nib instantiateWithOwner:self topLevelObjects:&topLevelObjects]) {
NSAssert(1, @"Failed to load nib file to show audio library items");
return nil;
}
for (id topLevelObject in topLevelObjects) {
if ([topLevelObject isKindOfClass:[VLCLibraryTableCellView class]]) {
cellView = topLevelObject;
break;
}
}
cellView.identifier = VLCAudioLibraryCellIdentifier;
}
VLCMediaLibraryMediaItem *mediaItem = _tracks[row];
NSImage *image = mediaItem.smallArtworkImage;
if (!image) {
image = [NSImage imageNamed: @"noart.png"];
}
cellView.representedImageView.image = image;
cellView.representedMediaItem = mediaItem;
NSString *title = mediaItem.title;
cellView.primaryTitleTextField.hidden = NO;
cellView.secondaryTitleTextField.hidden = NO;
cellView.primaryTitleTextField.stringValue = title;
cellView.secondaryTitleTextField.stringValue = [NSString stringWithTime:mediaItem.duration / 1000];
return cellView;
}
@end

View File

@ -30,14 +30,15 @@
#import "library/VLCLibraryTableCellView.h"
#import "library/VLCLibraryAlbumTableCellView.h"
#import "library/VLCLibraryCollectionViewItem.h"
#import "library/VLCLibraryCollectionViewFlowLayout.h"
#import "library/VLCLibraryCollectionViewAlbumSupplementaryDetailView.h"
#import "extensions/NSString+Helpers.h"
#import "views/VLCImageView.h"
static NSString *VLCAudioLibraryCellIdentifier = @"VLCAudioLibraryCellIdentifier";
@interface VLCLibraryAudioDataSource () <NSCollectionViewDelegate, NSCollectionViewDataSource>
{
VLCLibraryCollectionViewFlowLayout *_collectionViewFlowLayout;
NSInteger _currentSelectedSegment;
NSArray<NSString *> *_placeholderImageNames;
NSArray<NSString *> *_placeholderLabelStrings;
@ -67,12 +68,19 @@ static NSString *VLCAudioLibraryCellIdentifier = @"VLCAudioLibraryCellIdentifier
_collectionView.delegate = self;
[_collectionView registerClass:[VLCLibraryCollectionViewItem class] forItemWithIdentifier:VLCLibraryCellIdentifier];
NSCollectionViewFlowLayout *flowLayout = _collectionView.collectionViewLayout;
flowLayout.itemSize = CGSizeMake(214., 260.);
flowLayout.sectionInset = NSEdgeInsetsMake(20., 20., 20., 20.);
flowLayout.minimumLineSpacing = 20.;
flowLayout.minimumInteritemSpacing = 20.;
NSNib *albumSupplementaryDetailView = [[NSNib alloc] initWithNibNamed:@"VLCLibraryCollectionViewAlbumSupplementaryDetailView" bundle:nil];
[_collectionView registerNib:albumSupplementaryDetailView
forSupplementaryViewOfKind:VLCLibraryCollectionViewAlbumSupplementaryDetailViewKind
withIdentifier:VLCLibraryCollectionViewAlbumSupplementaryDetailViewIdentifier];
_collectionViewFlowLayout = [[VLCLibraryCollectionViewFlowLayout alloc] init];
_collectionView.collectionViewLayout = _collectionViewFlowLayout;
_collectionViewFlowLayout.itemSize = CGSizeMake(214., 260.);
_collectionViewFlowLayout.sectionInset = NSEdgeInsetsMake(20., 20., 20., 20.);
_collectionViewFlowLayout.minimumLineSpacing = 20.;
_collectionViewFlowLayout.minimumInteritemSpacing = 20.;
_groupSelectionTableView.target = self;
_groupSelectionTableView.doubleAction = @selector(groubSelectionDoubleClickAction:);
@ -112,6 +120,7 @@ static NSString *VLCAudioLibraryCellIdentifier = @"VLCAudioLibraryCellIdentifier
- (IBAction)segmentedControlAction:(id)sender
{
[_collectionViewFlowLayout resetLayout];
_currentSelectedSegment = _segmentedControl.selectedSegment;
switch (_currentSelectedSegment) {
case 0:
@ -400,6 +409,42 @@ static NSString *VLCAudioLibraryCellIdentifier = @"VLCAudioLibraryCellIdentifier
return viewItem;
}
- (void)collectionView:(NSCollectionView *)collectionView didSelectItemsAtIndexPaths:(NSSet<NSIndexPath *> *)indexPaths
{
NSIndexPath *indexPath = indexPaths.anyObject;
if (!indexPath || _currentParentType != VLC_ML_PARENT_ALBUM) {
return;
}
[_collectionViewFlowLayout expandDetailSectionAtIndex:indexPath];
}
- (void)collectionView:(NSCollectionView *)collectionView didDeselectItemsAtIndexPaths:(NSSet<NSIndexPath *> *)indexPaths
{
NSIndexPath *indexPath = indexPaths.anyObject;
if (!indexPath || _currentParentType != VLC_ML_PARENT_ALBUM) {
return;
}
[_collectionViewFlowLayout collapseDetailSectionAtIndex:indexPath];
}
- (NSView *)collectionView:(NSCollectionView *)collectionView
viewForSupplementaryElementOfKind:(NSCollectionViewSupplementaryElementKind)kind
atIndexPath:(NSIndexPath *)indexPath
{
if ([kind isEqualToString:VLCLibraryCollectionViewAlbumSupplementaryDetailViewKind] && _currentParentType == VLC_ML_PARENT_ALBUM) {
VLCLibraryCollectionViewAlbumSupplementaryDetailView* albumSupplementaryDetailView = [collectionView makeSupplementaryViewOfKind:kind withIdentifier:VLCLibraryCollectionViewAlbumSupplementaryDetailViewKind forIndexPath:indexPath];
VLCMediaLibraryAlbum *album = _displayedCollection[indexPath.item];
albumSupplementaryDetailView.representedAlbum = album;
return albumSupplementaryDetailView;
}
return nil;
}
@end
@implementation VLCLibraryGroupDataSource

View File

@ -0,0 +1,41 @@
/*****************************************************************************
* VLCLibraryCollectionViewSupplementaryDetailView.h: 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 <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
@class VLCMediaLibraryAlbum;
extern NSString *const VLCLibraryCollectionViewAlbumSupplementaryDetailViewIdentifier;
extern NSCollectionViewSupplementaryElementKind const VLCLibraryCollectionViewAlbumSupplementaryDetailViewKind;
@interface VLCLibraryCollectionViewAlbumSupplementaryDetailView : NSView <NSCollectionViewElement>
@property (readwrite, retain, nonatomic) VLCMediaLibraryAlbum *representedAlbum;
@property (readwrite, weak) IBOutlet NSTextField *albumTitleTextField;
@property (readwrite, weak) IBOutlet NSTextField *albumDetailsTextField;
@property (readwrite, weak) IBOutlet NSImageView *albumArtworkImageView;
@property (readwrite, weak) IBOutlet NSTableView *albumTracksTableView;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,84 @@
/*****************************************************************************
* VLCLibraryCollectionViewSupplementaryDetailView.m: MacOS X interface module
*****************************************************************************
* Copyright (C) 2021 VLC authors and VideoLAN
*
* Authors: Samuel Bassaly <shkshk90 # gmail -dot- com>
* 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 "VLCLibraryCollectionViewAlbumSupplementaryDetailView.h"
#import "main/VLCMain.h"
#import "library/VLCLibraryController.h"
#import "library/VLCLibraryDataTypes.h"
#import "library/VLCLibraryModel.h"
#import "library/VLCLibraryMenuController.h"
#import "views/VLCImageView.h"
#import "extensions/NSString+Helpers.h"
#import "extensions/NSFont+VLCAdditions.h"
#import "extensions/NSColor+VLCAdditions.h"
#import "extensions/NSView+VLCAdditions.h"
#import "library/VLCLibraryAlbumTracksDataSource.h"
#import "library/VLCLibraryAlbumTableCellView.h"
NSString *const VLCLibraryCollectionViewAlbumSupplementaryDetailViewIdentifier = @"VLCLibraryCollectionViewAlbumSupplementaryDetailViewIdentifier";
NSCollectionViewSupplementaryElementKind const VLCLibraryCollectionViewAlbumSupplementaryDetailViewKind = @"VLCLibraryCollectionViewAlbumSupplementaryDetailViewIdentifier";
@interface VLCLibraryCollectionViewAlbumSupplementaryDetailView ()
{
VLCLibraryAlbumTracksDataSource *_tracksDataSource;
}
@end
@implementation VLCLibraryCollectionViewAlbumSupplementaryDetailView
@synthesize representedAlbum = _representedAlbum;
@synthesize albumTitleTextField = _albumTitleTextField;
@synthesize albumDetailsTextField = _albumDetailsTextField;
@synthesize albumArtworkImageView = _albumArtworkImageView;
@synthesize albumTracksTableView = _albumTracksTableView;
- (void)awakeFromNib
{
_tracksDataSource = [[VLCLibraryAlbumTracksDataSource alloc] init];
_albumTracksTableView.dataSource = _tracksDataSource;
_albumTracksTableView.delegate = _tracksDataSource;
}
- (void)setRepresentedAlbum:(VLCMediaLibraryAlbum *)representedAlbum
{
_representedAlbum = representedAlbum;
[self updateRepresentation];
}
- (void)updateRepresentation
{
if (_representedAlbum == nil) {
NSAssert(1, @"no media item assigned for collection view item", nil);
return;
}
_albumTitleTextField.stringValue = _representedAlbum.displayString;
_albumDetailsTextField.stringValue = _representedAlbum.artistName;
_albumArtworkImageView.image = _representedAlbum.smallArtworkImage;
_tracksDataSource.representedAlbum = _representedAlbum;
[_albumTracksTableView reloadData];
}
@end

View File

@ -0,0 +1,35 @@
/*****************************************************************************
* VLCLibraryCollectionViewFlowLayout.h: 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 <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
@interface VLCLibraryCollectionViewFlowLayout : NSCollectionViewFlowLayout
- (void)expandDetailSectionAtIndex:(NSIndexPath *)indexPath;
- (void)collapseDetailSectionAtIndex:(NSIndexPath *)indexPath;
- (void)resetLayout;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,274 @@
/*****************************************************************************
* 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 "VLCLibraryCollectionViewAlbumSupplementaryDetailView.h"
#pragma mark - Private data
static const NSUInteger kAnimationSteps = 16;
static const NSUInteger kWrapAroundValue = (NSUInteger)-1;
static const CGFloat kDetailViewMargin = 8.;
static const CGFloat kDetailViewCollapsedHeight = 0.;
static const CGFloat kDetailViewExpandedHeight = 300.;
static const CGFloat kAnimationHeightIncrement = kDetailViewExpandedHeight / (CGFloat)kAnimationSteps;
typedef NS_ENUM(NSUInteger, VLCDetailViewAnimationType)
{
VLCDetailViewAnimationTypeExpand,
VLCDetailViewAnimationTypeCollapse,
};
static CVReturn detailViewAnimationCallback(CVDisplayLinkRef displayLink,
const CVTimeStamp *inNow,
const CVTimeStamp *inOutputTime,
CVOptionFlags flagsIn,
CVOptionFlags *flagsOut,
void *displayLinkContext);
#pragma mark - VLCLibraryCollectionViewFlowLayout
@interface VLCLibraryCollectionViewFlowLayout ()
{
NSUInteger _lastHeightIndex;
CVDisplayLinkRef _displayLinkRef;
}
@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)init
{
self = [super init];
if (self == nil) {
return nil;
}
[self resetLayout];
return self;
}
#pragma mark - Public methods
- (void)expandDetailSectionAtIndex:(NSIndexPath *)indexPath
{
if([_selectedIndexPath isEqual:indexPath]) {
return;
}
_selectedIndexPath = indexPath;
[self animateDetailViewWithAnimation:VLCDetailViewAnimationTypeExpand];
}
- (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
- (NSCollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSCollectionViewLayoutAttributes *attributes = [super layoutAttributesForItemAtIndexPath:indexPath];
if(_selectedIndexPath == nil || indexPath == _selectedIndexPath) {
return attributes;
}
[attributes setFrame:[self frameForDisplacedAttributes:attributes]];
return attributes;
}
- (NSArray<__kindof NSCollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(NSRect)rect
{
if (_selectedIndexPath == nil) {
return [super layoutAttributesForElementsInRect:rect];
}
NSRect selectedItemFrame = [[self layoutAttributesForItemAtIndexPath:_selectedIndexPath] frame];
// Computed attributes from parent
NSMutableArray<__kindof NSCollectionViewLayoutAttributes *> *layoutAttributesArray = [[super layoutAttributesForElementsInRect:rect] mutableCopy];
for (int i = 0; i < layoutAttributesArray.count; i++) {
NSCollectionViewLayoutAttributes *attributes = layoutAttributesArray[i];
[attributes setFrame:[self frameForDisplacedAttributes:attributes]];
layoutAttributesArray[i] = attributes;
}
// Add detail view to the attributes set -- detail view about to be shown
[layoutAttributesArray addObject:[self layoutAttributesForSupplementaryViewOfKind:VLCLibraryCollectionViewAlbumSupplementaryDetailViewKind atIndexPath:self.selectedIndexPath]];
return layoutAttributesArray;
}
- (NSCollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSCollectionViewSupplementaryElementKind)elementKind
atIndexPath:(NSIndexPath *)indexPath
{
if ([elementKind isEqualToString:VLCLibraryCollectionViewAlbumSupplementaryDetailViewKind]) {
NSCollectionViewLayoutAttributes *detailViewAttributes = [NSCollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:elementKind
withIndexPath:indexPath];
NSAssert1(detailViewAttributes != NULL,
@"Failed to create NSCollectionViewLayoutAttributes for view of kind %@.",
VLCLibraryCollectionViewAlbumSupplementaryDetailViewKind );
float selectedItemFrameMaxY = _selectedIndexPath == nil ? 0 : NSMaxY([[self layoutAttributesForItemAtIndexPath:_selectedIndexPath] frame]);
detailViewAttributes.frame = NSMakeRect(NSMinX(self.collectionView.frame),
selectedItemFrameMaxY + kDetailViewMargin,
self.collectionViewContentSize.width - 16.0,
(_animationIndex * kAnimationHeightIncrement));
return detailViewAttributes;
}
NSCollectionViewLayoutAttributes *attributes = [super layoutAttributesForSupplementaryViewOfKind:elementKind
atIndexPath:indexPath];
[attributes setFrame:[self frameForDisplacedAttributes:attributes]];
return attributes;
}
- (NSSet<NSIndexPath *> *)indexPathsToDeleteForSupplementaryViewOfKind:(NSString *)elementKind
{
if ([elementKind isEqualToString:VLCLibraryCollectionViewAlbumSupplementaryDetailViewKind]) {
return [self.collectionView indexPathsForVisibleSupplementaryElementsOfKind:elementKind];
}
return [NSSet set];
}
# pragma mark - Calculation of displaced frame attributes
- (NSRect)frameForDisplacedAttributes:(NSCollectionViewLayoutAttributes *)inAttributes {
NSRect attributesFrame = inAttributes.frame;
if (self.selectedIndexPath) {
NSRect selectedItemFrame = [[self layoutAttributesForItemAtIndexPath:_selectedIndexPath] frame];
if (NSMinY(attributesFrame) > (NSMaxY(selectedItemFrame))) {
attributesFrame.origin.y += (_animationIndex * kAnimationHeightIncrement) + kDetailViewMargin;
}
}
return attributesFrame;
}
#pragma mark - Detail view animation
- (void)animateDetailViewWithAnimation:(VLCDetailViewAnimationType)type
{
if (type == VLCDetailViewAnimationTypeExpand) {
_animationIsCollapse = NO;
_animationIndex = kWrapAroundValue;
_lastHeightIndex = kAnimationSteps - 1;
} else {
_animationIsCollapse = YES;
_animationIndex = kAnimationSteps;
_lastHeightIndex = 0;
}
_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,
const CVTimeStamp *inNow,
const CVTimeStamp *inOutputTime,
CVOptionFlags flagsIn,
CVOptionFlags *flagsOut,
void *displayLinkContext)
{
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;
}
}
dispatch_async(dispatch_get_main_queue(), ^(void){
[bridgedSelf invalidateLayout];
});
return kCVReturnSuccess;
}

View File

@ -322,6 +322,9 @@ static void addShadow(NSImageView *__unsafe_unretained imageView)
_audioGroupSelectionTableView.dataSource = _libraryAudioGroupDataSource;
_audioGroupSelectionTableView.delegate = _libraryAudioGroupDataSource;
_audioGroupSelectionTableView.rowHeight = [VLCLibraryAlbumTableCellView defaultHeight];
_audioLibraryCollectionView.selectable = YES;
_audioLibraryCollectionView.allowsMultipleSelection = NO;
_audioLibraryCollectionView.allowsEmptySelection = YES;
_mediaSourceDataSource = [[VLCMediaSourceBaseDataSource alloc] init];
_mediaSourceDataSource.collectionView = _mediaSourceCollectionView;