mirror of
https://github.com/danbee/persephone
synced 2025-03-04 08:39:11 +00:00
Add a button to albums that plays the album
This works by clearing the queue, adding all the tracks from the album to the queue and playing the first track. Fix bug where resize might leave a play button visible Had to refactor the QueueView somewhat as there was a bug that only surfaced on clearing and refilling the playlist. The bug was due to the way NSOutlineView reuses subviews. Add screenshot with album play button Move queue datasource and refactor view controller Move album datasource out of the view controller
This commit is contained in:
parent
5d0e1c4719
commit
2d6aa478a7
@ -16,20 +16,21 @@
|
||||
E408D3B9220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3B8220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift */; };
|
||||
E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3BD220E03EE0006D9BE /* RawRepresentable.swift */; };
|
||||
E408D3C2220E134F0006D9BE /* AlbumViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3C1220E134F0006D9BE /* AlbumViewController.swift */; };
|
||||
E408D3CA220E341D0006D9BE /* AlbumItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3C8220E341D0006D9BE /* AlbumItem.swift */; };
|
||||
E408D3CB220E341D0006D9BE /* AlbumItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = E408D3C9220E341D0006D9BE /* AlbumItem.xib */; };
|
||||
E40F41F3221EDE27004B6CB8 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = E40F41F2221EDE27004B6CB8 /* Preferences.swift */; };
|
||||
E40FE719221B48E300A4223F /* AlbumViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = E40FE718221B48E300A4223F /* AlbumViewLayout.swift */; };
|
||||
E40FE71B221B904300A4223F /* NSEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = E40FE71A221B904300A4223F /* NSEvent.swift */; };
|
||||
E41B22C021FB6BBA00D544F6 /* libmpdclient.2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */; settings = {ATTRIBUTES = (Required, ); }; };
|
||||
E41B22C121FB6C3300D544F6 /* libmpdclient.2.dylib in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
E41B22C621FB932700D544F6 /* MPDClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41B22C521FB932700D544F6 /* MPDClient.swift */; };
|
||||
E41EA46C221636AF0068EF46 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EA46B221636AF0068EF46 /* PreferencesViewController.swift */; };
|
||||
E41EA46F221715910068EF46 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EA46E221715910068EF46 /* Preferences.swift */; };
|
||||
E42A8F3B22176D6400A13ED9 /* LICENSE.md in Resources */ = {isa = PBXBuildFile; fileRef = E42A8F3922176D6400A13ED9 /* LICENSE.md */; };
|
||||
E42A8F3C22176D6400A13ED9 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = E42A8F3A22176D6400A13ED9 /* README.md */; };
|
||||
E465049A21E94DF500A70F4C /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E465049921E94DF500A70F4C /* WindowController.swift */; };
|
||||
E47E2FD122205C4600F747E6 /* MainSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FD022205C4600F747E6 /* MainSplitViewController.swift */; };
|
||||
E47E2FD322205D2500F747E6 /* MainWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FD222205D2500F747E6 /* MainWindow.swift */; };
|
||||
E47E2FD5222071FD00F747E6 /* AlbumItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FD4222071FD00F747E6 /* AlbumItem.swift */; };
|
||||
E47E2FD72220720300F747E6 /* AlbumItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FD62220720300F747E6 /* AlbumItemView.swift */; };
|
||||
E4928E0B2218D62A001D4BEA /* CGColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4928E0A2218D62A001D4BEA /* CGColor.swift */; };
|
||||
E4A642DA22090CBE00067D21 /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A642D922090CBE00067D21 /* Status.swift */; };
|
||||
E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC8F2204EC7F0024217A /* Delegate.swift */; };
|
||||
@ -38,6 +39,9 @@
|
||||
E4E8CC9A22075D370024217A /* Song.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC9922075D370024217A /* Song.swift */; };
|
||||
E4EB2379220F10B8008C70C0 /* Pair.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4EB2378220F10B8008C70C0 /* Pair.swift */; };
|
||||
E4EB237B220F7CF1008C70C0 /* Album.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4EB237A220F7CF1008C70C0 /* Album.swift */; };
|
||||
E4F6B460221E119B00ACF42A /* QueueDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B45F221E119B00ACF42A /* QueueDataSource.swift */; };
|
||||
E4F6B463221E125900ACF42A /* SongItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B462221E125900ACF42A /* SongItem.swift */; };
|
||||
E4F6B467221E233200ACF42A /* AlbumDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B466221E233200ACF42A /* AlbumDataSource.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@ -88,8 +92,8 @@
|
||||
E408D3B8220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSUserInterfaceItemIdentifier.swift; sourceTree = "<group>"; };
|
||||
E408D3BD220E03EE0006D9BE /* RawRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawRepresentable.swift; sourceTree = "<group>"; };
|
||||
E408D3C1220E134F0006D9BE /* AlbumViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumViewController.swift; sourceTree = "<group>"; };
|
||||
E408D3C8220E341D0006D9BE /* AlbumItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumItem.swift; sourceTree = "<group>"; };
|
||||
E408D3C9220E341D0006D9BE /* AlbumItem.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AlbumItem.xib; sourceTree = "<group>"; };
|
||||
E40F41F2221EDE27004B6CB8 /* Preferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
|
||||
E40FE718221B48E300A4223F /* AlbumViewLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumViewLayout.swift; sourceTree = "<group>"; };
|
||||
E40FE71A221B904300A4223F /* NSEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSEvent.swift; sourceTree = "<group>"; };
|
||||
E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libmpdclient.2.dylib; path = libmpdclient/output/libmpdclient.2.dylib; sourceTree = "<group>"; };
|
||||
@ -131,12 +135,13 @@
|
||||
E41B22EA21FB966C00D544F6 /* queue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = queue.h; sourceTree = "<group>"; };
|
||||
E41B22EB21FB966C00D544F6 /* playlist.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = playlist.h; sourceTree = "<group>"; };
|
||||
E41EA46B221636AF0068EF46 /* PreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesViewController.swift; sourceTree = "<group>"; };
|
||||
E41EA46E221715910068EF46 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
|
||||
E42A8F3922176D6400A13ED9 /* LICENSE.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = "<group>"; };
|
||||
E42A8F3A22176D6400A13ED9 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||
E465049921E94DF500A70F4C /* WindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowController.swift; sourceTree = "<group>"; };
|
||||
E47E2FD022205C4600F747E6 /* MainSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSplitViewController.swift; sourceTree = "<group>"; };
|
||||
E47E2FD222205D2500F747E6 /* MainWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainWindow.swift; sourceTree = "<group>"; };
|
||||
E47E2FD4222071FD00F747E6 /* AlbumItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumItem.swift; sourceTree = "<group>"; };
|
||||
E47E2FD62220720300F747E6 /* AlbumItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumItemView.swift; sourceTree = "<group>"; };
|
||||
E4928E0A2218D62A001D4BEA /* CGColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGColor.swift; sourceTree = "<group>"; };
|
||||
E4A642D922090CBE00067D21 /* Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Status.swift; sourceTree = "<group>"; };
|
||||
E4E8CC8F2204EC7F0024217A /* Delegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Delegate.swift; sourceTree = "<group>"; };
|
||||
@ -145,6 +150,9 @@
|
||||
E4E8CC9922075D370024217A /* Song.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Song.swift; sourceTree = "<group>"; };
|
||||
E4EB2378220F10B8008C70C0 /* Pair.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pair.swift; sourceTree = "<group>"; };
|
||||
E4EB237A220F7CF1008C70C0 /* Album.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Album.swift; sourceTree = "<group>"; };
|
||||
E4F6B45F221E119B00ACF42A /* QueueDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueDataSource.swift; sourceTree = "<group>"; };
|
||||
E4F6B462221E125900ACF42A /* SongItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SongItem.swift; sourceTree = "<group>"; };
|
||||
E4F6B466221E233200ACF42A /* AlbumDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumDataSource.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -200,7 +208,8 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E40FE717221B48CE00A4223F /* Layouts */,
|
||||
E41EA46D221715820068EF46 /* Models */,
|
||||
E4F6B461221E124700ACF42A /* Models */,
|
||||
E4F6B45E221E117600ACF42A /* DataSources */,
|
||||
E407861F2110CE70006887B1 /* Assets.xcassets */,
|
||||
E408D3B7220DE8CC0006D9BE /* Extensions */,
|
||||
E4D1B598220BA3C90026F233 /* Resources */,
|
||||
@ -255,7 +264,7 @@
|
||||
E408D3C3220E138B0006D9BE /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E408D3C8220E341D0006D9BE /* AlbumItem.swift */,
|
||||
E47E2FD62220720300F747E6 /* AlbumItemView.swift */,
|
||||
E47E2FD222205D2500F747E6 /* MainWindow.swift */,
|
||||
);
|
||||
path = Views;
|
||||
@ -328,14 +337,6 @@
|
||||
path = mpd;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E41EA46D221715820068EF46 /* Models */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E41EA46E221715910068EF46 /* Preferences.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E4A642DB220912FA00067D21 /* MPDClient */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -369,6 +370,7 @@
|
||||
E4D1B597220BA3A20026F233 /* Controllers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E47E2FD4222071FD00F747E6 /* AlbumItem.swift */,
|
||||
E408D3C1220E134F0006D9BE /* AlbumViewController.swift */,
|
||||
E41EA46B221636AF0068EF46 /* PreferencesViewController.swift */,
|
||||
E4E8CC932206097F0024217A /* NotificationsController.swift */,
|
||||
@ -388,6 +390,24 @@
|
||||
path = Resources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E4F6B45E221E117600ACF42A /* DataSources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E4F6B45F221E119B00ACF42A /* QueueDataSource.swift */,
|
||||
E4F6B466221E233200ACF42A /* AlbumDataSource.swift */,
|
||||
);
|
||||
path = DataSources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E4F6B461221E124700ACF42A /* Models */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E40F41F2221EDE27004B6CB8 /* Preferences.swift */,
|
||||
E4F6B462221E125900ACF42A /* SongItem.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@ -530,25 +550,29 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
E4F6B467221E233200ACF42A /* AlbumDataSource.swift in Sources */,
|
||||
E408D3C2220E134F0006D9BE /* AlbumViewController.swift in Sources */,
|
||||
E40FE71B221B904300A4223F /* NSEvent.swift in Sources */,
|
||||
E4928E0B2218D62A001D4BEA /* CGColor.swift in Sources */,
|
||||
E4F6B460221E119B00ACF42A /* QueueDataSource.swift in Sources */,
|
||||
E4A642DA22090CBE00067D21 /* Status.swift in Sources */,
|
||||
E47E2FD72220720300F747E6 /* AlbumItemView.swift in Sources */,
|
||||
E4E8CC942206097F0024217A /* NotificationsController.swift in Sources */,
|
||||
E40FE719221B48E300A4223F /* AlbumViewLayout.swift in Sources */,
|
||||
E408D3B6220DD8970006D9BE /* Notification.swift in Sources */,
|
||||
E408D3B9220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift in Sources */,
|
||||
E4EB2379220F10B8008C70C0 /* Pair.swift in Sources */,
|
||||
E41EA46F221715910068EF46 /* Preferences.swift in Sources */,
|
||||
E4F6B463221E125900ACF42A /* SongItem.swift in Sources */,
|
||||
E465049A21E94DF500A70F4C /* WindowController.swift in Sources */,
|
||||
E41B22C621FB932700D544F6 /* MPDClient.swift in Sources */,
|
||||
E40F41F3221EDE27004B6CB8 /* Preferences.swift in Sources */,
|
||||
E407861C2110CE6E006887B1 /* AppDelegate.swift in Sources */,
|
||||
E47E2FD322205D2500F747E6 /* MainWindow.swift in Sources */,
|
||||
E47E2FD122205C4600F747E6 /* MainSplitViewController.swift in Sources */,
|
||||
E4E8CC9A22075D370024217A /* Song.swift in Sources */,
|
||||
E408D3CA220E341D0006D9BE /* AlbumItem.swift in Sources */,
|
||||
E41EA46C221636AF0068EF46 /* PreferencesViewController.swift in Sources */,
|
||||
E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */,
|
||||
E47E2FD5222071FD00F747E6 /* AlbumItem.swift in Sources */,
|
||||
E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */,
|
||||
E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */,
|
||||
E4EB237B220F7CF1008C70C0 /* Album.swift in Sources */,
|
||||
|
||||
25
Persephone/Assets.xcassets/playButtonLarge.imageset/Contents.json
vendored
Normal file
25
Persephone/Assets.xcassets/playButtonLarge.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "playButtonLarge.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "playButtonLarge@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
BIN
Persephone/Assets.xcassets/playButtonLarge.imageset/playButtonLarge.png
vendored
Normal file
BIN
Persephone/Assets.xcassets/playButtonLarge.imageset/playButtonLarge.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 295 B |
BIN
Persephone/Assets.xcassets/playButtonLarge.imageset/playButtonLarge@2x.png
vendored
Normal file
BIN
Persephone/Assets.xcassets/playButtonLarge.imageset/playButtonLarge@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 516 B |
@ -10,6 +10,7 @@ import Cocoa
|
||||
|
||||
class AlbumItem: NSCollectionViewItem {
|
||||
var observer: NSKeyValueObservation?
|
||||
var album: MPDClient.Album?
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
@ -27,6 +28,7 @@ class AlbumItem: NSCollectionViewItem {
|
||||
}
|
||||
|
||||
func setAlbum(_ album: MPDClient.Album) {
|
||||
self.album = album
|
||||
albumTitle.stringValue = album.title
|
||||
albumArtist.stringValue = album.artist
|
||||
}
|
||||
@ -42,6 +44,12 @@ class AlbumItem: NSCollectionViewItem {
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func playAlbum(_ sender: Any) {
|
||||
guard let album = album else { return }
|
||||
|
||||
AppDelegate.mpdClient.playAlbum(album)
|
||||
}
|
||||
|
||||
@IBOutlet var albumCoverView: NSImageView!
|
||||
@IBOutlet var albumTitle: NSTextField!
|
||||
@IBOutlet var albumArtist: NSTextField!
|
||||
@ -9,16 +9,18 @@
|
||||
import Cocoa
|
||||
|
||||
class AlbumViewController: NSViewController,
|
||||
NSCollectionViewDataSource,
|
||||
NSCollectionViewDelegate,
|
||||
NSCollectionViewDelegateFlowLayout {
|
||||
var albums: [MPDClient.Album] = []
|
||||
let paddingWidth: CGFloat = 40
|
||||
let gutterWidth: CGFloat = 20
|
||||
|
||||
var dataSource = AlbumDataSource()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
albumScrollView.postsBoundsChangedNotifications = true
|
||||
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(updateAlbums(_:)),
|
||||
@ -32,6 +34,8 @@ class AlbumViewController: NSViewController,
|
||||
name: Notification.willDisconnect,
|
||||
object: AppDelegate.mpdClient
|
||||
)
|
||||
|
||||
albumCollectionView.dataSource = dataSource
|
||||
}
|
||||
|
||||
override func viewWillLayout() {
|
||||
@ -44,30 +48,16 @@ class AlbumViewController: NSViewController,
|
||||
guard let albums = notification.userInfo?[Notification.albumsKey] as? [MPDClient.Album]
|
||||
else { return }
|
||||
|
||||
self.albums = albums
|
||||
|
||||
dataSource.albums = albums
|
||||
albumCollectionView.reloadData()
|
||||
}
|
||||
|
||||
@objc func clearAlbums(_ notification: Notification) {
|
||||
self.albums = []
|
||||
dataSource.albums = []
|
||||
|
||||
albumCollectionView.reloadData()
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return albums.count
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
|
||||
let item = collectionView.makeItem(withIdentifier: .albumItem, for: indexPath)
|
||||
guard let albumItem = item as? AlbumItem else { return item }
|
||||
|
||||
albumItem.view.wantsLayer = true
|
||||
albumItem.setAlbum(albums[indexPath.item])
|
||||
|
||||
return albumItem
|
||||
}
|
||||
|
||||
@IBOutlet var albumScrollView: NSScrollView!
|
||||
@IBOutlet var albumCollectionView: NSCollectionView!
|
||||
}
|
||||
|
||||
@ -8,28 +8,140 @@
|
||||
|
||||
import Cocoa
|
||||
|
||||
class QueueViewController: NSViewController, NSOutlineViewDataSource, NSOutlineViewDelegate {
|
||||
var queue: [MPDClient.Song] = []
|
||||
var queuePos: Int = -1
|
||||
|
||||
var queueIcon: NSImage? = nil
|
||||
class QueueViewController: NSViewController,
|
||||
NSOutlineViewDelegate {
|
||||
var dataSource = QueueDataSource()
|
||||
|
||||
let systemFontRegular = NSFont.systemFont(ofSize: 13, weight: .regular)
|
||||
let systemFontBold = NSFont.systemFont(ofSize: 13, weight: .bold)
|
||||
|
||||
|
||||
let playIcon = NSImage(named: "playButton")
|
||||
let pauseIcon = NSImage(named: "pauseButton")
|
||||
|
||||
struct SongItem {
|
||||
var song: MPDClient.Song
|
||||
var queuePos: Int
|
||||
}
|
||||
@IBOutlet var queueView: NSOutlineView!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
queueView.columnAutoresizingStyle = .sequentialColumnAutoresizingStyle
|
||||
setupNotificationObservers()
|
||||
|
||||
queueView.dataSource = dataSource
|
||||
queueView.columnAutoresizingStyle = .sequentialColumnAutoresizingStyle
|
||||
}
|
||||
|
||||
override func keyDown(with event: NSEvent) {
|
||||
switch event.keyCode {
|
||||
case NSEvent.keyCodeSpace:
|
||||
nextResponder?.keyDown(with: event)
|
||||
default:
|
||||
super.keyDown(with: event)
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func playTrack(_ sender: Any) {
|
||||
if dataSource.queuePos >= 0 {
|
||||
AppDelegate.mpdClient.playTrack(queuePos: dataSource.queuePos)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func stateChanged(_ notification: Notification) {
|
||||
guard let state = notification.userInfo?[Notification.stateKey] as? MPDClient.Status.State
|
||||
else { return }
|
||||
|
||||
dataSource.setQueueIcon(state)
|
||||
}
|
||||
|
||||
@objc func queueChanged(_ notification: Notification) {
|
||||
guard let queue = notification.userInfo?[Notification.queueKey] as? [MPDClient.Song]
|
||||
else { return }
|
||||
|
||||
dataSource.updateQueue(queue)
|
||||
queueView.reloadData()
|
||||
}
|
||||
|
||||
@objc func queuePosChanged(_ notification: Notification) {
|
||||
guard let queuePos = notification.userInfo?[Notification.queuePosKey] as? Int
|
||||
else { return }
|
||||
|
||||
dataSource.setQueuePos(queuePos)
|
||||
|
||||
queueView.reloadData()
|
||||
}
|
||||
|
||||
func outlineView(
|
||||
_ outlineView: NSOutlineView,
|
||||
selectionIndexesForProposedSelection proposedSelectionIndexes: IndexSet
|
||||
) -> IndexSet {
|
||||
if proposedSelectionIndexes.contains(0) {
|
||||
return IndexSet()
|
||||
} else {
|
||||
return proposedSelectionIndexes
|
||||
}
|
||||
}
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
|
||||
if let songItem = item as? SongItem {
|
||||
switch tableColumn?.identifier.rawValue {
|
||||
case "songTitleColumn":
|
||||
return cellForSongTitle(outlineView, with: songItem)
|
||||
case "songArtistColumn":
|
||||
return cellForSongArtist(outlineView, with: songItem)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
} else if tableColumn?.identifier.rawValue == "songTitleColumn" {
|
||||
return cellForQueueHeading(outlineView)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func cellForSongTitle(_ outlineView: NSOutlineView, with songItem: SongItem) -> NSView {
|
||||
let cellView = outlineView.makeView(
|
||||
withIdentifier: .queueSongTitle,
|
||||
owner: self
|
||||
) as! NSTableCellView
|
||||
|
||||
cellView.textField?.stringValue = songItem.song.getTag(.title)
|
||||
if songItem.isPlaying {
|
||||
cellView.textField?.font = systemFontBold
|
||||
cellView.imageView?.image = dataSource.queueIcon
|
||||
} else {
|
||||
cellView.textField?.font = systemFontRegular
|
||||
cellView.imageView?.image = nil
|
||||
}
|
||||
|
||||
return cellView
|
||||
}
|
||||
|
||||
func cellForSongArtist(_ outlineView: NSOutlineView, with songItem: SongItem) -> NSView {
|
||||
let cellView = outlineView.makeView(
|
||||
withIdentifier: .queueSongArtist,
|
||||
owner: self
|
||||
) as! NSTableCellView
|
||||
|
||||
cellView.textField?.stringValue = songItem.song.getTag(.artist)
|
||||
if songItem.isPlaying {
|
||||
cellView.textField?.font = systemFontBold
|
||||
} else {
|
||||
cellView.textField?.font = systemFontRegular
|
||||
}
|
||||
|
||||
return cellView
|
||||
}
|
||||
|
||||
func cellForQueueHeading(_ outlineView: NSOutlineView) -> NSView {
|
||||
let cellView = outlineView.makeView(
|
||||
withIdentifier: .queueHeading,
|
||||
owner: self
|
||||
) as! NSTableCellView
|
||||
|
||||
cellView.textField?.stringValue = "QUEUE"
|
||||
|
||||
return cellView
|
||||
}
|
||||
|
||||
func setupNotificationObservers() {
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(stateChanged(_:)),
|
||||
@ -50,177 +162,5 @@ class QueueViewController: NSViewController, NSOutlineViewDataSource, NSOutlineV
|
||||
name: Notification.queuePosChanged,
|
||||
object: AppDelegate.mpdClient
|
||||
)
|
||||
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(clearQueue(_:)),
|
||||
name: Notification.willDisconnect,
|
||||
object: AppDelegate.mpdClient
|
||||
)
|
||||
}
|
||||
|
||||
override func keyDown(with event: NSEvent) {
|
||||
switch event.keyCode {
|
||||
case NSEvent.keyCodeSpace:
|
||||
nextResponder?.keyDown(with: event)
|
||||
default:
|
||||
super.keyDown(with: event)
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func playTrack(_ sender: Any) {
|
||||
guard let view = sender as? NSOutlineView
|
||||
else { return }
|
||||
|
||||
let queuePos = view.selectedRow - 1
|
||||
|
||||
if queuePos >= 0 {
|
||||
AppDelegate.mpdClient.playTrack(queuePos: queuePos)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func stateChanged(_ notification: Notification) {
|
||||
guard let state = notification.userInfo?[Notification.stateKey] as? MPDClient.Status.State
|
||||
else { return }
|
||||
|
||||
setQueueIcon(state)
|
||||
}
|
||||
|
||||
@objc func queueChanged(_ notification: Notification) {
|
||||
guard let queue = notification.userInfo?[Notification.queueKey] as? [MPDClient.Song]
|
||||
else { return }
|
||||
|
||||
self.queue = queue
|
||||
|
||||
queueView.reloadData()
|
||||
}
|
||||
|
||||
@objc func queuePosChanged(_ notification: Notification) {
|
||||
guard let queuePos = notification.userInfo?[Notification.queuePosKey] as? Int
|
||||
else { return }
|
||||
|
||||
let oldSongRowPos = self.queuePos + 1
|
||||
let newSongRowPos = queuePos + 1
|
||||
self.queuePos = queuePos
|
||||
|
||||
setQueuePos(oldSongRowPos: oldSongRowPos, newSongRowPos: newSongRowPos)
|
||||
|
||||
queueView.reloadData(
|
||||
forRowIndexes: [oldSongRowPos, newSongRowPos],
|
||||
columnIndexes: [0, 1]
|
||||
)
|
||||
}
|
||||
|
||||
@objc func clearQueue(_ notification: Notification) {
|
||||
self.queue = []
|
||||
|
||||
queueView.reloadData()
|
||||
}
|
||||
|
||||
func setQueueIcon(_ state: MPDClient.Status.State) {
|
||||
switch state {
|
||||
case .playing:
|
||||
self.queueIcon = playIcon
|
||||
case .paused:
|
||||
self.queueIcon = pauseIcon
|
||||
default:
|
||||
self.queueIcon = nil
|
||||
}
|
||||
}
|
||||
|
||||
func setQueuePos(oldSongRowPos: Int, newSongRowPos: Int) {
|
||||
if oldSongRowPos > 0 {
|
||||
guard let oldSongRow = queueView.rowView(atRow: oldSongRowPos, makeIfNecessary: true),
|
||||
let oldSongTitleCell = oldSongRow.view(atColumn: 0) as? NSTableCellView
|
||||
else { return }
|
||||
|
||||
setRowFont(rowView: oldSongRow, font: systemFontRegular)
|
||||
oldSongTitleCell.imageView?.image = nil
|
||||
}
|
||||
|
||||
guard let songRow = queueView.rowView(atRow: newSongRowPos, makeIfNecessary: true),
|
||||
let newSongTitleCell = songRow.view(atColumn: 0) as? NSTableCellView
|
||||
else { return }
|
||||
|
||||
setRowFont(rowView: songRow, font: systemFontBold)
|
||||
newSongTitleCell.imageView?.image = self.queueIcon
|
||||
}
|
||||
|
||||
func setRowFont(rowView: NSTableRowView, font: NSFont) {
|
||||
guard let songTitleCell = rowView.view(atColumn: 0) as? NSTableCellView,
|
||||
let songArtistCell = rowView.view(atColumn: 1) as? NSTableCellView
|
||||
else { return }
|
||||
|
||||
songTitleCell.textField?.font = font
|
||||
songArtistCell.textField?.font = font
|
||||
}
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
|
||||
return queue.count + 1
|
||||
}
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
|
||||
if index > 0 {
|
||||
return SongItem(song: queue[index - 1], queuePos: index - 1)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
|
||||
if let songItem = item as? SongItem {
|
||||
switch tableColumn?.identifier.rawValue {
|
||||
case "songTitleColumn":
|
||||
let cellView = outlineView.makeView(
|
||||
withIdentifier: .queueSongTitle,
|
||||
owner: self
|
||||
) as! NSTableCellView
|
||||
|
||||
cellView.textField?.stringValue = songItem.song.getTag(.title)
|
||||
|
||||
return cellView
|
||||
case "songArtistColumn":
|
||||
let cellView = outlineView.makeView(
|
||||
withIdentifier: .queueSongArtist,
|
||||
owner: self
|
||||
) as! NSTableCellView
|
||||
|
||||
cellView.textField?.stringValue = songItem.song.getTag(.artist)
|
||||
|
||||
return cellView
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
if tableColumn?.identifier.rawValue == "songTitleColumn" {
|
||||
let cellView = outlineView.makeView(
|
||||
withIdentifier: .queueHeading,
|
||||
owner: self
|
||||
) as! NSTableCellView
|
||||
|
||||
cellView.textField?.stringValue = "QUEUE"
|
||||
|
||||
return cellView
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func outlineView(
|
||||
_ outlineView: NSOutlineView,
|
||||
selectionIndexesForProposedSelection proposedSelectionIndexes: IndexSet
|
||||
) -> IndexSet {
|
||||
if proposedSelectionIndexes.contains(0) {
|
||||
return IndexSet()
|
||||
} else {
|
||||
return proposedSelectionIndexes
|
||||
}
|
||||
}
|
||||
|
||||
@IBOutlet var queueView: NSOutlineView!
|
||||
}
|
||||
|
||||
27
Persephone/DataSources/AlbumDataSource.swift
Normal file
27
Persephone/DataSources/AlbumDataSource.swift
Normal file
@ -0,0 +1,27 @@
|
||||
//
|
||||
// AlbumDataSource.swift
|
||||
// Persephone
|
||||
//
|
||||
// Created by Daniel Barber on 2019/2/20.
|
||||
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class AlbumDataSource: NSObject, NSCollectionViewDataSource {
|
||||
var albums: [MPDClient.Album] = []
|
||||
|
||||
func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return albums.count
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
|
||||
let item = collectionView.makeItem(withIdentifier: .albumItem, for: indexPath)
|
||||
guard let albumItem = item as? AlbumItem else { return item }
|
||||
|
||||
albumItem.view.wantsLayer = true
|
||||
albumItem.setAlbum(albums[indexPath.item])
|
||||
|
||||
return albumItem
|
||||
}
|
||||
}
|
||||
65
Persephone/DataSources/QueueDataSource.swift
Normal file
65
Persephone/DataSources/QueueDataSource.swift
Normal file
@ -0,0 +1,65 @@
|
||||
//
|
||||
// QueueDataSource.swift
|
||||
// Persephone
|
||||
//
|
||||
// Created by Daniel Barber on 2019/2/20.
|
||||
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class QueueDataSource: NSObject, NSOutlineViewDataSource {
|
||||
var queue: [SongItem] = []
|
||||
var queuePos: Int = -1
|
||||
|
||||
var queueIcon: NSImage? = nil
|
||||
|
||||
let playIcon = NSImage(named: "playButton")
|
||||
let pauseIcon = NSImage(named: "pauseButton")
|
||||
|
||||
func updateQueue(_ queue: [MPDClient.Song]) {
|
||||
self.queue = queue.enumerated().map { index, song in
|
||||
SongItem(song: song, queuePos: index, isPlaying: index == queuePos)
|
||||
}
|
||||
}
|
||||
|
||||
func setQueuePos(_ queuePos: Int) {
|
||||
let oldSongRowPos = self.queuePos
|
||||
let newSongRowPos = queuePos
|
||||
self.queuePos = queuePos
|
||||
|
||||
if oldSongRowPos >= 0 {
|
||||
queue[oldSongRowPos].isPlaying = false
|
||||
}
|
||||
if newSongRowPos >= 0 {
|
||||
queue[newSongRowPos].isPlaying = true
|
||||
}
|
||||
}
|
||||
|
||||
func setQueueIcon(_ state: MPDClient.Status.State) {
|
||||
switch state {
|
||||
case .playing:
|
||||
queueIcon = playIcon
|
||||
case .paused:
|
||||
queueIcon = pauseIcon
|
||||
default:
|
||||
queueIcon = nil
|
||||
}
|
||||
}
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
|
||||
return queue.count + 1
|
||||
}
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
|
||||
if index > 0 {
|
||||
return queue[index - 1]
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -119,6 +119,27 @@ class MPDClient {
|
||||
idle()
|
||||
}
|
||||
|
||||
func playAlbum(_ album: Album) {
|
||||
noIdle()
|
||||
commandQueue.async { [unowned self] in
|
||||
var songs: [Song] = []
|
||||
|
||||
mpd_run_clear(self.connection)
|
||||
mpd_search_db_songs(self.connection, true)
|
||||
mpd_search_add_tag_constraint(self.connection, MPD_OPERATOR_DEFAULT, MPD_TAG_ALBUM, album.title)
|
||||
mpd_search_add_tag_constraint(self.connection, MPD_OPERATOR_DEFAULT, MPD_TAG_ALBUM_ARTIST, album.artist)
|
||||
mpd_search_commit(self.connection)
|
||||
while let mpdSong = mpd_recv_song(self.connection) {
|
||||
songs.append(Song(mpdSong))
|
||||
}
|
||||
for song in songs {
|
||||
mpd_run_add(self.connection, song.uri)
|
||||
}
|
||||
mpd_run_play_pos(self.connection, 0)
|
||||
}
|
||||
idle()
|
||||
}
|
||||
|
||||
func queueCommand(command: Command) {
|
||||
guard isConnected else { return }
|
||||
|
||||
|
||||
@ -41,6 +41,10 @@ extension MPDClient {
|
||||
mpd_song_free(mpdSong)
|
||||
}
|
||||
|
||||
var uri: UnsafePointer<Int8> {
|
||||
return mpd_song_get_uri(mpdSong)
|
||||
}
|
||||
|
||||
func getTag(_ tagType: TagType) -> String {
|
||||
let mpdTagType = mpd_tag_type(rawValue: Int32(tagType.rawValue))
|
||||
|
||||
|
||||
15
Persephone/Models/SongItem.swift
Normal file
15
Persephone/Models/SongItem.swift
Normal file
@ -0,0 +1,15 @@
|
||||
//
|
||||
// SongItem.swift
|
||||
// Persephone
|
||||
//
|
||||
// Created by Daniel Barber on 2019/2/20.
|
||||
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct SongItem {
|
||||
var song: MPDClient.Song
|
||||
var queuePos: Int
|
||||
var isPlaying: Bool
|
||||
}
|
||||
@ -9,7 +9,7 @@
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="AlbumItem" customModule="Persephone" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="albumArtist" destination="5Uu-j1-qyT" id="2Et-tX-InT"/>
|
||||
<outlet property="albumCoverView" destination="Kfb-8f-ean" id="ZAL-jD-lHj"/>
|
||||
<outlet property="albumCoverView" destination="Kfb-8f-ean" id="CXx-gB-gz8"/>
|
||||
<outlet property="albumTitle" destination="KEh-NL-c2W" id="SI3-hm-H2B"/>
|
||||
<outlet property="imageView" destination="Kfb-8f-ean" id="Ur0-hX-wJm"/>
|
||||
<outlet property="view" destination="Hz6-mo-xeY" id="v7W-XA-Emc"/>
|
||||
@ -17,7 +17,7 @@
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<customView id="Hz6-mo-xeY">
|
||||
<customView id="Hz6-mo-xeY" customClass="AlbumItemView" customModule="Persephone" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="128" height="167"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
@ -41,24 +41,45 @@
|
||||
<rect key="frame" x="0.0" y="39" width="128" height="128"/>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyUpOrDown" image="blankAlbum" id="FsA-JX-BFh"/>
|
||||
</imageView>
|
||||
<button hidden="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="n8W-do-HyG">
|
||||
<rect key="frame" x="43" y="81" width="42" height="43"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="42" id="XXC-YE-Ego"/>
|
||||
<constraint firstAttribute="width" constant="42" id="zcR-GT-zym"/>
|
||||
</constraints>
|
||||
<buttonCell key="cell" type="inline" bezelStyle="inline" image="playButtonLarge" imagePosition="only" alignment="center" lineBreakMode="truncatingTail" state="on" borderStyle="border" inset="2" id="T1p-LZ-RpJ">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="smallSystemBold"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="playAlbum:" target="-2" id="gNt-Rn-kte"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="5Uu-j1-qyT" firstAttribute="trailing" secondItem="KEh-NL-c2W" secondAttribute="trailing" id="64z-uz-4nY"/>
|
||||
<constraint firstAttribute="bottom" secondItem="KEh-NL-c2W" secondAttribute="bottom" constant="18" id="8Kg-1r-wNp"/>
|
||||
<constraint firstItem="Kfb-8f-ean" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" id="JMi-4i-dgs"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Kfb-8f-ean" secondAttribute="trailing" id="KQC-Wz-Bsg"/>
|
||||
<constraint firstItem="n8W-do-HyG" firstAttribute="centerX" secondItem="KEh-NL-c2W" secondAttribute="centerX" id="Kf1-ws-d4q"/>
|
||||
<constraint firstItem="5Uu-j1-qyT" firstAttribute="leading" secondItem="KEh-NL-c2W" secondAttribute="leading" id="MUo-0i-fX9"/>
|
||||
<constraint firstItem="Kfb-8f-ean" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" id="Qbk-jx-zAi"/>
|
||||
<constraint firstItem="KEh-NL-c2W" firstAttribute="trailing" secondItem="Kfb-8f-ean" secondAttribute="trailing" id="U0w-G4-ggX"/>
|
||||
<constraint firstItem="KEh-NL-c2W" firstAttribute="leading" secondItem="Kfb-8f-ean" secondAttribute="leading" id="V8r-Rc-Dx7"/>
|
||||
<constraint firstAttribute="bottom" secondItem="5Uu-j1-qyT" secondAttribute="bottom" id="gci-4h-pDZ"/>
|
||||
<constraint firstItem="n8W-do-HyG" firstAttribute="centerY" secondItem="Kfb-8f-ean" secondAttribute="centerY" id="pgP-oA-Nxa"/>
|
||||
<constraint firstAttribute="bottom" secondItem="Kfb-8f-ean" secondAttribute="bottom" constant="39" id="sid-zJ-YMA"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<outlet property="imageView" destination="Kfb-8f-ean" id="T7Z-En-dU3"/>
|
||||
<outlet property="playButton" destination="n8W-do-HyG" id="Xw0-iI-svx"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="-22" y="125.5"/>
|
||||
</customView>
|
||||
<collectionViewItem id="Qgu-aI-55A" customClass="AlbumItem" customModule="Persephone" customModuleProvider="target"/>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="blankAlbum" width="128" height="128"/>
|
||||
<image name="playButtonLarge" width="22" height="22"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@ -932,7 +932,7 @@
|
||||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
</tableColumn>
|
||||
<tableColumn identifier="songArtistColumn" width="144" minWidth="128" maxWidth="1000" id="SPM-QP-DX8">
|
||||
<tableColumn identifier="songArtistColumn" width="144" minWidth="64" maxWidth="1000" id="SPM-QP-DX8">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Artist">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
@ -949,9 +949,8 @@
|
||||
<rect key="frame" x="204" y="1" width="144" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tBe-Q9-3Rw">
|
||||
<rect key="frame" x="0.0" y="0.0" width="222" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="tBe-Q9-3Rw">
|
||||
<rect key="frame" x="0.0" y="0.0" width="149" height="17"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="Ceb-ec-ydU">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
@ -959,6 +958,11 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="tBe-Q9-3Rw" firstAttribute="centerY" secondItem="JSk-Vc-Y7e" secondAttribute="centerY" id="Tkg-cb-Bg6"/>
|
||||
<constraint firstAttribute="trailing" secondItem="tBe-Q9-3Rw" secondAttribute="trailing" constant="-3" id="VhZ-ua-QQX"/>
|
||||
<constraint firstItem="tBe-Q9-3Rw" firstAttribute="leading" secondItem="JSk-Vc-Y7e" secondAttribute="leading" constant="2" id="cTy-tR-Grg"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<outlet property="textField" destination="tBe-Q9-3Rw" id="2e6-zi-tKj"/>
|
||||
</connections>
|
||||
@ -968,7 +972,6 @@
|
||||
</tableColumns>
|
||||
<connections>
|
||||
<action trigger="doubleAction" selector="playTrack:" target="KIP-rq-4dM" id="opa-6G-OW0"/>
|
||||
<outlet property="dataSource" destination="KIP-rq-4dM" id="K1Q-7o-xXW"/>
|
||||
<outlet property="delegate" destination="KIP-rq-4dM" id="60F-6x-bUE"/>
|
||||
</connections>
|
||||
</outlineView>
|
||||
@ -1024,7 +1027,6 @@
|
||||
<collectionViewLayout key="collectionViewLayout" id="YE8-sD-l5P" customClass="AlbumViewLayout" customModule="Persephone" customModuleProvider="target"/>
|
||||
<color key="primaryBackgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="gPn-fP-LFc" id="2VB-5V-ltv"/>
|
||||
<outlet property="delegate" destination="gPn-fP-LFc" id="LQ2-Vl-r08"/>
|
||||
</connections>
|
||||
</collectionView>
|
||||
@ -1049,6 +1051,7 @@
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="albumCollectionView" destination="lfq-AB-epE" id="p69-Fs-hCN"/>
|
||||
<outlet property="albumScrollView" destination="i5f-35-7x8" id="jmd-Sa-Bxt"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<customObject id="uex-Ws-5X4" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
|
||||
71
Persephone/Views/AlbumItemView.swift
Normal file
71
Persephone/Views/AlbumItemView.swift
Normal file
@ -0,0 +1,71 @@
|
||||
//
|
||||
// AlbumItemView.swift
|
||||
// Persephone
|
||||
//
|
||||
// Created by Daniel Barber on 2019/2/17.
|
||||
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class AlbumItemView: NSView {
|
||||
var trackingArea: NSTrackingArea?
|
||||
|
||||
override func updateTrackingAreas() {
|
||||
super.updateTrackingAreas()
|
||||
|
||||
guard let albumImageView = imageView else { return }
|
||||
|
||||
if let trackingArea = self.trackingArea {
|
||||
self.removeTrackingArea(trackingArea)
|
||||
}
|
||||
|
||||
let trackingArea = NSTrackingArea(
|
||||
rect: albumImageView.frame,
|
||||
options: [.mouseEnteredAndExited, .activeAlways],
|
||||
owner: self,
|
||||
userInfo: nil
|
||||
)
|
||||
|
||||
self.trackingArea = trackingArea
|
||||
addTrackingArea(trackingArea)
|
||||
}
|
||||
|
||||
required init?(coder decoder: NSCoder) {
|
||||
super.init(coder: decoder)
|
||||
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(viewWillScroll(_:)),
|
||||
name: NSScrollView.willStartLiveScrollNotification,
|
||||
object: nil
|
||||
)
|
||||
}
|
||||
|
||||
@objc func viewWillScroll(_ notification: Notification) {
|
||||
hidePlayButton()
|
||||
}
|
||||
|
||||
override func resize(withOldSuperviewSize oldSize: NSSize) {
|
||||
hidePlayButton()
|
||||
}
|
||||
|
||||
override func mouseEntered(with event: NSEvent) {
|
||||
showPlayButton()
|
||||
}
|
||||
|
||||
override func mouseExited(with event: NSEvent) {
|
||||
hidePlayButton()
|
||||
}
|
||||
|
||||
func showPlayButton() {
|
||||
playButton.isHidden = false
|
||||
}
|
||||
|
||||
func hidePlayButton() {
|
||||
playButton.isHidden = true
|
||||
}
|
||||
|
||||
@IBOutlet var imageView: NSImageView!
|
||||
@IBOutlet var playButton: NSButton!
|
||||
}
|
||||
BIN
Resources/export/playButtonLarge.pdf
Normal file
BIN
Resources/export/playButtonLarge.pdf
Normal file
Binary file not shown.
BIN
Resources/export/playButtonLarge.png
Normal file
BIN
Resources/export/playButtonLarge.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 295 B |
BIN
Resources/export/playButtonLarge@2x.png
Normal file
BIN
Resources/export/playButtonLarge@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 516 B |
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 168 KiB After Width: | Height: | Size: 179 KiB |
Loading…
Reference in New Issue
Block a user