mirror of
https://github.com/danbee/persephone
synced 2025-03-04 08:39:11 +00:00
Compare commits
9 Commits
4beddf4a63
...
e05698e766
| Author | SHA1 | Date | |
|---|---|---|---|
| e05698e766 | |||
| 44f2b1c238 | |||
| 7de6d59594 | |||
| 338dde08d3 | |||
| 998ddb60bc | |||
| 0ee299a9c8 | |||
| 2bac18187c | |||
| 31b764905a | |||
| 63c55e1bd4 |
@ -39,6 +39,9 @@
|
||||
E435E3E2221CD4E200184CFC /* NSFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435E3E1221CD4E200184CFC /* NSFont.swift */; };
|
||||
E435E3E4221CD75D00184CFC /* NSImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435E3E3221CD75D00184CFC /* NSImage.swift */; };
|
||||
E439109822640213002982E9 /* SongNotifierService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E439109722640213002982E9 /* SongNotifierService.swift */; };
|
||||
E43B67AA22909793007DCF55 /* AlbumDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43B67A822909793007DCF55 /* AlbumDetailView.swift */; };
|
||||
E43B67AB22909793007DCF55 /* AlbumDetailView.xib in Resources */ = {isa = PBXBuildFile; fileRef = E43B67A922909793007DCF55 /* AlbumDetailView.xib */; };
|
||||
E43B67AD229194CD007DCF55 /* AlbumTracksDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43B67AC229194CD007DCF55 /* AlbumTracksDataSource.swift */; };
|
||||
E4405192227644340090CD6F /* MPDServerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4405191227644340090CD6F /* MPDServerController.swift */; };
|
||||
E44051942278765A0090CD6F /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = E44051932278765A0090CD6F /* App.swift */; };
|
||||
E4405196227879960090CD6F /* MPDActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4405195227879960090CD6F /* MPDActions.swift */; };
|
||||
@ -53,6 +56,7 @@
|
||||
E450AD9522262DF10091BED3 /* CoverArtQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = E450AD9422262DF10091BED3 /* CoverArtQueue.swift */; };
|
||||
E450AD98222633920091BED3 /* Alamofire.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E450AD96222633920091BED3 /* Alamofire.framework.dSYM */; };
|
||||
E450ADA12229E7C90091BED3 /* PMKFoundation.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E450AD9F2229E7C90091BED3 /* PMKFoundation.framework.dSYM */; };
|
||||
E45878382296173C00586A1C /* AlbumDetailSongRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E45878372296173C00586A1C /* AlbumDetailSongRowView.swift */; };
|
||||
E45962C62241A78500FC1A1E /* MPDCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = E45962C52241A78500FC1A1E /* MPDCommand.swift */; };
|
||||
E45E4FDA22515D87004B537F /* CHANGELOG.md in Resources */ = {isa = PBXBuildFile; fileRef = E45E4FD722515D87004B537F /* CHANGELOG.md */; };
|
||||
E45E4FDB22515D87004B537F /* Brewfile in Resources */ = {isa = PBXBuildFile; fileRef = E45E4FD822515D87004B537F /* Brewfile */; };
|
||||
@ -67,6 +71,7 @@
|
||||
E47E2FDD2220A6D100F747E6 /* Time.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FDC2220A6D100F747E6 /* Time.swift */; };
|
||||
E47E2FE52220AA0700F747E6 /* AlbumViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FE42220AA0700F747E6 /* AlbumViewLayout.swift */; };
|
||||
E4928E0B2218D62A001D4BEA /* CGColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4928E0A2218D62A001D4BEA /* CGColor.swift */; };
|
||||
E4A3A6A122A457B600EA2C40 /* AlbumDetailSongListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A3A6A022A457B600EA2C40 /* AlbumDetailSongListView.swift */; };
|
||||
E4A642DA22090CBE00067D21 /* MPDStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A642D922090CBE00067D21 /* MPDStatus.swift */; };
|
||||
E4A83BEF2221F8CF0098FED6 /* CoverArtPrefsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BEE2221F8CF0098FED6 /* CoverArtPrefsController.swift */; };
|
||||
E4A83BF12221FAA00098FED6 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */; };
|
||||
@ -245,6 +250,9 @@
|
||||
E435E3E1221CD4E200184CFC /* NSFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSFont.swift; sourceTree = "<group>"; };
|
||||
E435E3E3221CD75D00184CFC /* NSImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSImage.swift; sourceTree = "<group>"; };
|
||||
E439109722640213002982E9 /* SongNotifierService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SongNotifierService.swift; sourceTree = "<group>"; };
|
||||
E43B67A822909793007DCF55 /* AlbumDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumDetailView.swift; sourceTree = "<group>"; };
|
||||
E43B67A922909793007DCF55 /* AlbumDetailView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AlbumDetailView.xib; sourceTree = "<group>"; };
|
||||
E43B67AC229194CD007DCF55 /* AlbumTracksDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumTracksDataSource.swift; sourceTree = "<group>"; };
|
||||
E4405191227644340090CD6F /* MPDServerController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDServerController.swift; sourceTree = "<group>"; };
|
||||
E44051932278765A0090CD6F /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
|
||||
E4405195227879960090CD6F /* MPDActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDActions.swift; sourceTree = "<group>"; };
|
||||
@ -264,6 +272,7 @@
|
||||
E450AD9E2229B9BC0091BED3 /* PersephoneBridgingHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PersephoneBridgingHeader.h; sourceTree = "<group>"; };
|
||||
E450AD9F2229E7C90091BED3 /* PMKFoundation.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = PMKFoundation.framework.dSYM; path = Carthage/Build/Mac/PMKFoundation.framework.dSYM; sourceTree = "<group>"; };
|
||||
E450ADA02229E7C90091BED3 /* PMKFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PMKFoundation.framework; path = Carthage/Build/Mac/PMKFoundation.framework; sourceTree = "<group>"; };
|
||||
E45878372296173C00586A1C /* AlbumDetailSongRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumDetailSongRowView.swift; sourceTree = "<group>"; };
|
||||
E45962C52241A78500FC1A1E /* MPDCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDCommand.swift; sourceTree = "<group>"; };
|
||||
E45E4FD722515D87004B537F /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = SOURCE_ROOT; };
|
||||
E45E4FD822515D87004B537F /* Brewfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Brewfile; sourceTree = SOURCE_ROOT; };
|
||||
@ -279,6 +288,7 @@
|
||||
E47E2FDC2220A6D100F747E6 /* Time.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Time.swift; sourceTree = "<group>"; };
|
||||
E47E2FE42220AA0700F747E6 /* AlbumViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumViewLayout.swift; sourceTree = "<group>"; };
|
||||
E4928E0A2218D62A001D4BEA /* CGColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGColor.swift; sourceTree = "<group>"; };
|
||||
E4A3A6A022A457B600EA2C40 /* AlbumDetailSongListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumDetailSongListView.swift; sourceTree = "<group>"; };
|
||||
E4A642D922090CBE00067D21 /* MPDStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDStatus.swift; sourceTree = "<group>"; };
|
||||
E4A83BEE2221F8CF0098FED6 /* CoverArtPrefsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverArtPrefsController.swift; sourceTree = "<group>"; };
|
||||
E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesViewController.swift; sourceTree = "<group>"; };
|
||||
@ -458,6 +468,8 @@
|
||||
E47E2FD222205D2500F747E6 /* MainWindow.swift */,
|
||||
E4B11BB7227538FA0075461B /* CurrentCoverArtView.swift */,
|
||||
E423563F228623D2001216D6 /* QueueSongTitleView.swift */,
|
||||
E45878372296173C00586A1C /* AlbumDetailSongRowView.swift */,
|
||||
E4A3A6A022A457B600EA2C40 /* AlbumDetailSongListView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
@ -670,6 +682,7 @@
|
||||
E4D1B597220BA3A20026F233 /* Controllers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E43B67A822909793007DCF55 /* AlbumDetailView.swift */,
|
||||
E408D3C1220E134F0006D9BE /* AlbumViewController.swift */,
|
||||
E47E2FD4222071FD00F747E6 /* AlbumViewItem.swift */,
|
||||
E47E2FD022205C4600F747E6 /* MainSplitViewController.swift */,
|
||||
@ -685,6 +698,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E40786212110CE70006887B1 /* Main.storyboard */,
|
||||
E43B67A922909793007DCF55 /* AlbumDetailView.xib */,
|
||||
E408D3C9220E341D0006D9BE /* AlbumViewItem.xib */,
|
||||
);
|
||||
path = Resources;
|
||||
@ -695,6 +709,7 @@
|
||||
children = (
|
||||
E4F6B466221E233200ACF42A /* AlbumDataSource.swift */,
|
||||
E4F6B45F221E119B00ACF42A /* QueueDataSource.swift */,
|
||||
E43B67AC229194CD007DCF55 /* AlbumTracksDataSource.swift */,
|
||||
);
|
||||
path = DataSources;
|
||||
sourceTree = "<group>";
|
||||
@ -835,6 +850,7 @@
|
||||
E450AD9122262C780091BED3 /* SwiftyJSON.framework.dSYM in Resources */,
|
||||
E42A8F3B22176D6400A13ED9 /* LICENSE.md in Resources */,
|
||||
E45E4FDA22515D87004B537F /* CHANGELOG.md in Resources */,
|
||||
E43B67AB22909793007DCF55 /* AlbumDetailView.xib in Resources */,
|
||||
E45E4FDC22515D87004B537F /* Cartfile in Resources */,
|
||||
E450AD98222633920091BED3 /* Alamofire.framework.dSYM in Resources */,
|
||||
E40786202110CE70006887B1 /* Assets.xcassets in Resources */,
|
||||
@ -915,6 +931,7 @@
|
||||
E4B11BC02275EE150075461B /* QueueActions.swift in Sources */,
|
||||
E47E2FD72220720300F747E6 /* AlbumItemView.swift in Sources */,
|
||||
E450AD9522262DF10091BED3 /* CoverArtQueue.swift in Sources */,
|
||||
E43B67AD229194CD007DCF55 /* AlbumTracksDataSource.swift in Sources */,
|
||||
E41E52FD223BF87300173814 /* MPDClient+Connection.swift in Sources */,
|
||||
E450AD7E222620A10091BED3 /* Album.swift in Sources */,
|
||||
E408D3B6220DD8970006D9BE /* Notification.swift in Sources */,
|
||||
@ -922,6 +939,7 @@
|
||||
E408D3B9220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift in Sources */,
|
||||
E4EB2379220F10B8008C70C0 /* MPDPair.swift in Sources */,
|
||||
E440519E227BB0720090CD6F /* UIReducer.swift in Sources */,
|
||||
E43B67AA22909793007DCF55 /* AlbumDetailView.swift in Sources */,
|
||||
E4FF7190227601B400D4C412 /* PreferencesReducer.swift in Sources */,
|
||||
E4F6B463221E125900ACF42A /* QueueItem.swift in Sources */,
|
||||
E4A83BF12221FAA00098FED6 /* PreferencesViewController.swift in Sources */,
|
||||
@ -958,10 +976,12 @@
|
||||
E41E530E223EF4CF00173814 /* CoverArtService+Caching.swift in Sources */,
|
||||
E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */,
|
||||
E440519C227BAF2E0090CD6F /* UIActions.swift in Sources */,
|
||||
E45878382296173C00586A1C /* AlbumDetailSongRowView.swift in Sources */,
|
||||
E41E5312223EF74A00173814 /* CoverArtService+Filesystem.swift in Sources */,
|
||||
E41E5301223BF99300173814 /* MPDClient+Queue.swift in Sources */,
|
||||
E4EB237B220F7CF1008C70C0 /* MPDAlbum.swift in Sources */,
|
||||
E41E5303223BF9C300173814 /* MPDClient+Idle.swift in Sources */,
|
||||
E4A3A6A122A457B600EA2C40 /* AlbumDetailSongListView.swift in Sources */,
|
||||
E4B11B53226928F20075461B /* AppState.swift in Sources */,
|
||||
E435E3E4221CD75D00184CFC /* NSImage.swift in Sources */,
|
||||
E440519A22787CF60090CD6F /* MPDReducer.swift in Sources */,
|
||||
|
||||
222
Persephone/Controllers/AlbumDetailView.swift
Normal file
222
Persephone/Controllers/AlbumDetailView.swift
Normal file
@ -0,0 +1,222 @@
|
||||
//
|
||||
// AlbumDetailView.swift
|
||||
// Persephone
|
||||
//
|
||||
// Created by Daniel Barber on 2019/5/18.
|
||||
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class AlbumDetailView: NSViewController {
|
||||
var album: Album?
|
||||
var dataSource = AlbumTracksDataSource()
|
||||
|
||||
static let shared = AlbumDetailView()
|
||||
static let popover = NSPopover()
|
||||
|
||||
@IBOutlet var albumTracksView: NSTableView!
|
||||
@IBOutlet var albumTitle: NSTextField!
|
||||
@IBOutlet var albumArtist: NSTextField!
|
||||
@IBOutlet var albumCoverView: NSImageView!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
albumTracksView.dataSource = dataSource
|
||||
albumTracksView.delegate = self
|
||||
albumTracksView.intercellSpacing = CGSize(width: 0, height: 18)
|
||||
albumTracksView.floatsGroupRows = false
|
||||
albumTracksView.columnAutoresizingStyle = .sequentialColumnAutoresizingStyle
|
||||
|
||||
albumCoverView.wantsLayer = true
|
||||
albumCoverView.layer?.cornerRadius = 5
|
||||
albumCoverView.layer?.borderWidth = 1
|
||||
setAppearance()
|
||||
|
||||
}
|
||||
|
||||
override func viewWillAppear() {
|
||||
guard let album = album else { return }
|
||||
|
||||
getAlbumSongs(for: album)
|
||||
|
||||
albumTitle.stringValue = album.title
|
||||
albumArtist.stringValue = album.artist
|
||||
|
||||
switch album.coverArt {
|
||||
case .loaded(let coverArt):
|
||||
albumCoverView.image = coverArt ?? .defaultCoverArt
|
||||
default:
|
||||
albumCoverView.image = .defaultCoverArt
|
||||
}
|
||||
|
||||
super.viewWillAppear()
|
||||
}
|
||||
|
||||
override func viewWillDisappear() {
|
||||
dataSource.albumSongs = []
|
||||
albumTracksView.reloadData()
|
||||
albumTitle.stringValue = ""
|
||||
albumArtist.stringValue = ""
|
||||
albumCoverView.image = .defaultCoverArt
|
||||
}
|
||||
|
||||
@IBAction func playAlbum(_ sender: NSButton) {
|
||||
guard let album = album else { return }
|
||||
|
||||
App.store.dispatch(MPDPlayAlbum(album: album.mpdAlbum))
|
||||
}
|
||||
|
||||
@IBAction func playSong(_ sender: AlbumDetailSongListView) {
|
||||
guard let song = dataSource.albumSongs[sender.selectedRow].song
|
||||
else { return }
|
||||
|
||||
let queueLength = App.store.state.queueState.queue.count
|
||||
App.store.dispatch(MPDAppendTrack(song: song.mpdSong))
|
||||
App.store.dispatch(MPDPlayTrack(queuePos: queueLength))
|
||||
}
|
||||
|
||||
@IBAction func menuActionPlaySong(_ sender: NSMenuItem) {
|
||||
guard let song = dataSource.albumSongs[albumTracksView.clickedRow].song
|
||||
else { return }
|
||||
|
||||
let queueLength = App.store.state.queueState.queue.count
|
||||
App.store.dispatch(MPDAppendTrack(song: song.mpdSong))
|
||||
App.store.dispatch(MPDPlayTrack(queuePos: queueLength))
|
||||
}
|
||||
|
||||
@IBAction func menuActionAppendSong(_ sender: NSMenuItem) {
|
||||
guard let song = dataSource.albumSongs[albumTracksView.clickedRow].song
|
||||
else { return }
|
||||
|
||||
App.store.dispatch(MPDAppendTrack(song: song.mpdSong))
|
||||
}
|
||||
|
||||
func getAlbumSongs(for album: Album) {
|
||||
App.mpdClient.getAlbumSongs(for: album.mpdAlbum) { [weak self] (mpdSongs: [MPDClient.MPDSong]) in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.dataSource.setAlbumSongs(
|
||||
mpdSongs.map { Song(mpdSong: $0) }
|
||||
)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.albumTracksView.reloadData()
|
||||
}
|
||||
|
||||
guard let song = self.dataSource.albumSongs.first?.song ??
|
||||
self.dataSource.albumSongs[1].song
|
||||
else { return }
|
||||
|
||||
self.getBigCoverArt(song: song)
|
||||
}
|
||||
}
|
||||
|
||||
func getBigCoverArt(song: Song) {
|
||||
let coverArtService = CoverArtService(song: song)
|
||||
|
||||
coverArtService.fetchBigCoverArt()
|
||||
.done(on: DispatchQueue.main) { [weak self] image in
|
||||
if let image = image {
|
||||
self?.albumCoverView.image = image
|
||||
}
|
||||
}
|
||||
.cauterize()
|
||||
}
|
||||
|
||||
func setAppearance() {
|
||||
if #available(OSX 10.14, *) {
|
||||
let darkMode = NSApp.effectiveAppearance.bestMatch(from:
|
||||
[.darkAqua, .aqua]) == .darkAqua
|
||||
|
||||
albumCoverView.layer?.borderColor = darkMode ? .albumBorderColorDark : .albumBorderColorLight
|
||||
} else {
|
||||
albumCoverView.layer?.borderColor = .albumBorderColorLight
|
||||
}
|
||||
}
|
||||
|
||||
func setAlbum(_ album: Album) {
|
||||
self.album = album
|
||||
}
|
||||
}
|
||||
|
||||
extension AlbumDetailView: NSTableViewDelegate {
|
||||
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
|
||||
if let song = dataSource.albumSongs[row].song {
|
||||
switch tableColumn?.identifier.rawValue {
|
||||
case "trackNumberColumn":
|
||||
return cellForTrackNumber(tableView, with: song)
|
||||
case "trackTitleColumn":
|
||||
return cellForSongTitle(tableView, with: song)
|
||||
case "trackDurationColumn":
|
||||
return cellForSongDuration(tableView, with: song)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
} else if let disc = dataSource.albumSongs[row].disc {
|
||||
return cellForDiscNumber(tableView, with: disc)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func tableView(_ tableView: NSTableView, isGroupRow row: Int) -> Bool {
|
||||
return dataSource.albumSongs[row].disc != nil
|
||||
}
|
||||
|
||||
func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? {
|
||||
let view = AlbumDetailSongRowView()
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
|
||||
return dataSource.albumSongs[row].disc == nil
|
||||
}
|
||||
|
||||
func cellForDiscNumber(_ tableView: NSTableView, with disc: String) -> NSView {
|
||||
let cellView = tableView.makeView(
|
||||
withIdentifier: .discNumber,
|
||||
owner: self
|
||||
) as! NSTableCellView
|
||||
|
||||
cellView.textField?.stringValue = "Disc \(disc)"
|
||||
|
||||
return cellView
|
||||
}
|
||||
|
||||
func cellForTrackNumber(_ tableView: NSTableView, with song: Song) -> NSView {
|
||||
let cellView = tableView.makeView(
|
||||
withIdentifier: .trackNumber,
|
||||
owner: self
|
||||
) as! NSTableCellView
|
||||
|
||||
cellView.textField?.stringValue = "\(song.trackNumber)."
|
||||
|
||||
return cellView
|
||||
}
|
||||
|
||||
func cellForSongTitle(_ tableView: NSTableView, with song: Song) -> NSView {
|
||||
let cellView = tableView.makeView(
|
||||
withIdentifier: .songTitle,
|
||||
owner: self
|
||||
) as! NSTableCellView
|
||||
|
||||
cellView.textField?.stringValue = song.title
|
||||
|
||||
return cellView
|
||||
}
|
||||
|
||||
func cellForSongDuration(_ tableView: NSTableView, with song: Song) -> NSView {
|
||||
let cellView = tableView.makeView(
|
||||
withIdentifier: .songDuration,
|
||||
owner: self
|
||||
) as! NSTableCellView
|
||||
|
||||
cellView.textField?.font = .timerFont
|
||||
cellView.textField?.stringValue = song.duration.formattedTime
|
||||
|
||||
return cellView
|
||||
}
|
||||
}
|
||||
@ -27,6 +27,12 @@ class AlbumViewItem: NSCollectionViewItem {
|
||||
}
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
|
||||
AlbumDetailView.popover.close()
|
||||
}
|
||||
|
||||
func setAlbum(_ album: Album) {
|
||||
self.album = album
|
||||
albumTitle.stringValue = album.title
|
||||
@ -51,13 +57,21 @@ class AlbumViewItem: NSCollectionViewItem {
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func playAlbum(_ sender: Any) {
|
||||
@IBAction func showAlbumDetail(_ sender: NSButton) {
|
||||
guard let album = album else { return }
|
||||
|
||||
App.store.dispatch(MPDPlayAlbum(album: album.mpdAlbum))
|
||||
AlbumDetailView.shared.setAlbum(album)
|
||||
|
||||
AlbumDetailView.popover.contentViewController = AlbumDetailView.shared
|
||||
AlbumDetailView.popover.behavior = .transient
|
||||
AlbumDetailView.popover.show(
|
||||
relativeTo: sender.bounds,
|
||||
of: sender,
|
||||
preferredEdge: .maxY
|
||||
)
|
||||
}
|
||||
|
||||
@IBOutlet var albumCoverView: NSImageView!
|
||||
@IBOutlet var albumCoverView: NSButton!
|
||||
@IBOutlet var albumTitle: NSTextField!
|
||||
@IBOutlet var albumArtist: NSTextField!
|
||||
}
|
||||
|
||||
45
Persephone/DataSources/AlbumTracksDataSource.swift
Normal file
45
Persephone/DataSources/AlbumTracksDataSource.swift
Normal file
@ -0,0 +1,45 @@
|
||||
//
|
||||
// AlbumTracksDataSource.swift
|
||||
// Persephone
|
||||
//
|
||||
// Created by Daniel Barber on 2019/5/19.
|
||||
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
|
||||
class AlbumTracksDataSource: NSObject, NSTableViewDataSource {
|
||||
struct AlbumSongItem {
|
||||
let disc: String?
|
||||
let song: Song?
|
||||
|
||||
init(song: Song) {
|
||||
self.disc = nil
|
||||
self.song = song
|
||||
}
|
||||
|
||||
init(disc: String) {
|
||||
self.disc = disc
|
||||
self.song = nil
|
||||
}
|
||||
}
|
||||
|
||||
var albumSongs: [AlbumSongItem] = []
|
||||
|
||||
func setAlbumSongs(_ songs: [Song]) {
|
||||
var disc: String? = ""
|
||||
|
||||
songs.forEach { song in
|
||||
if song.disc != disc {
|
||||
disc = song.disc
|
||||
albumSongs.append(AlbumSongItem(disc: song.disc))
|
||||
}
|
||||
|
||||
albumSongs.append(AlbumSongItem(song: song))
|
||||
}
|
||||
}
|
||||
|
||||
func numberOfRows(in tableView: NSTableView) -> Int {
|
||||
return albumSongs.count
|
||||
}
|
||||
}
|
||||
@ -17,4 +17,9 @@ extension NSUserInterfaceItemIdentifier {
|
||||
static let queueSongTitle = NSUserInterfaceItemIdentifier("songTitleCell")
|
||||
|
||||
static let albumViewItem = NSUserInterfaceItemIdentifier("AlbumViewItem")
|
||||
|
||||
static let discNumber = NSUserInterfaceItemIdentifier("discNumberCell")
|
||||
static let trackNumber = NSUserInterfaceItemIdentifier("trackNumberCell")
|
||||
static let songTitle = NSUserInterfaceItemIdentifier("songTitleCell")
|
||||
static let songDuration = NSUserInterfaceItemIdentifier("songDurationCell")
|
||||
}
|
||||
|
||||
@ -26,21 +26,27 @@ extension MPDClient {
|
||||
)
|
||||
}
|
||||
|
||||
func sendPlayAlbum(_ album: MPDAlbum) {
|
||||
var songs: [MPDSong] = []
|
||||
func getAlbumSongs(for album: MPDAlbum, callback: @escaping ([MPDSong]) -> Void) {
|
||||
enqueueCommand(
|
||||
command: .getAlbumSongs,
|
||||
priority: .normal,
|
||||
userData: ["album": album, "callback": callback]
|
||||
)
|
||||
}
|
||||
|
||||
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 song = mpd_recv_song(self.connection) {
|
||||
songs.append(MPDSong(song))
|
||||
func sendPlayAlbum(_ album: MPDAlbum) {
|
||||
getAlbumSongs(for: album) { songs in
|
||||
self.enqueueCommand(
|
||||
command: .replaceQueue,
|
||||
priority: .normal,
|
||||
userData: ["songs": songs]
|
||||
)
|
||||
self.enqueueCommand(
|
||||
command: .playTrack,
|
||||
priority: .normal,
|
||||
userData: ["queuePos": 0]
|
||||
)
|
||||
}
|
||||
for song in songs {
|
||||
mpd_run_add(self.connection, song.uri)
|
||||
}
|
||||
mpd_run_play_pos(self.connection, 0)
|
||||
}
|
||||
|
||||
func allAlbums() {
|
||||
@ -91,4 +97,21 @@ extension MPDClient {
|
||||
|
||||
callback(firstSong)
|
||||
}
|
||||
|
||||
func albumSongs(for album: MPDAlbum, callback: ([MPDSong]) -> Void) {
|
||||
guard isConnected else { return }
|
||||
|
||||
var songs: [MPDSong] = []
|
||||
|
||||
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 song = mpd_recv_song(self.connection) {
|
||||
songs.append(MPDSong(song))
|
||||
}
|
||||
|
||||
callback(songs)
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,6 +54,14 @@ extension MPDClient {
|
||||
guard let queuePos = userData["queuePos"] as? Int
|
||||
else { return }
|
||||
sendPlayTrack(at: queuePos)
|
||||
case .replaceQueue:
|
||||
guard let songs = userData["songs"] as? [MPDSong]
|
||||
else { return }
|
||||
sendReplaceQueue(songs)
|
||||
case .appendSong:
|
||||
guard let song = userData["song"] as? MPDSong
|
||||
else { return }
|
||||
sendAppendSong(song)
|
||||
|
||||
// Album commands
|
||||
case .fetchAllAlbums:
|
||||
@ -67,6 +75,13 @@ extension MPDClient {
|
||||
else { return }
|
||||
|
||||
albumFirstSong(for: album, callback: callback)
|
||||
|
||||
case .getAlbumSongs:
|
||||
guard let album = userData["album"] as? MPDAlbum,
|
||||
let callback = userData["callback"] as? ([MPDSong]) -> Void
|
||||
else { return }
|
||||
|
||||
albumSongs(for: album, callback: callback)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -18,6 +18,10 @@ extension MPDClient {
|
||||
enqueueCommand(command: .playTrack, userData: ["queuePos": queuePos])
|
||||
}
|
||||
|
||||
func appendSong(_ song: MPDSong) {
|
||||
enqueueCommand(command: .appendSong, userData: ["song": song])
|
||||
}
|
||||
|
||||
func sendPlayTrack(at queuePos: Int) {
|
||||
mpd_run_play_pos(self.connection, UInt32(queuePos))
|
||||
}
|
||||
@ -31,4 +35,17 @@ extension MPDClient {
|
||||
self.queue.append(song)
|
||||
}
|
||||
}
|
||||
|
||||
func sendReplaceQueue(_ songs: [MPDSong]) {
|
||||
mpd_run_clear(self.connection)
|
||||
|
||||
for song in songs {
|
||||
mpd_run_add(self.connection, song.uri)
|
||||
}
|
||||
mpd_run_play_pos(self.connection, 0)
|
||||
}
|
||||
|
||||
func sendAppendSong(_ song: MPDSong) {
|
||||
mpd_run_add(self.connection, song.uri)
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,10 +29,13 @@ extension MPDClient {
|
||||
// Queue commands
|
||||
case fetchQueue
|
||||
case playTrack
|
||||
case replaceQueue
|
||||
case appendSong
|
||||
|
||||
// Album commands
|
||||
case fetchAllAlbums
|
||||
case playAlbum
|
||||
case getAlbumFirstSong
|
||||
case getAlbumSongs
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,6 +49,10 @@ extension MPDClient {
|
||||
return String(cString: uri)
|
||||
}
|
||||
|
||||
var duration: Int {
|
||||
return Int(mpd_song_get_duration(song))
|
||||
}
|
||||
|
||||
var album: MPDAlbum {
|
||||
return MPDAlbum(
|
||||
title: getTag(.album),
|
||||
|
||||
@ -11,6 +11,14 @@ import Foundation
|
||||
struct Song {
|
||||
var mpdSong: MPDClient.MPDSong
|
||||
|
||||
var disc: String {
|
||||
return mpdSong.getTag(.disc)
|
||||
}
|
||||
|
||||
var trackNumber: String {
|
||||
return mpdSong.getTag(.track)
|
||||
}
|
||||
|
||||
var title: String {
|
||||
return mpdSong.getTag(.title)
|
||||
}
|
||||
@ -19,6 +27,10 @@ struct Song {
|
||||
return mpdSong.getTag(.artist)
|
||||
}
|
||||
|
||||
var duration: Time {
|
||||
return Time(timeInSeconds: mpdSong.duration)
|
||||
}
|
||||
|
||||
var album: Album {
|
||||
return Album(mpdAlbum: mpdSong.album)
|
||||
}
|
||||
|
||||
273
Persephone/Resources/AlbumDetailView.xib
Normal file
273
Persephone/Resources/AlbumDetailView.xib
Normal file
@ -0,0 +1,273 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="AlbumDetailView" customModule="Persephone" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="albumArtist" destination="4Jx-I5-Nkv" id="mct-x3-yYC"/>
|
||||
<outlet property="albumCoverView" destination="FWd-vZ-5CT" id="aHh-Bz-XQW"/>
|
||||
<outlet property="albumTitle" destination="m2v-pR-e9v" id="M5i-u6-Nev"/>
|
||||
<outlet property="albumTracksView" destination="ehr-qh-87Q" id="fSa-Di-CqI"/>
|
||||
<outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<customView id="Hz6-mo-xeY">
|
||||
<rect key="frame" x="0.0" y="0.0" width="823" height="568"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="m2v-pR-e9v">
|
||||
<rect key="frame" x="357" y="514" width="448" height="29"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="444" id="erC-QS-9hc"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" title="Album Title" id="URb-mh-vZz">
|
||||
<font key="font" metaFont="systemSemibold" size="24"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="4Jx-I5-Nkv">
|
||||
<rect key="frame" x="357" y="487" width="448" height="19"/>
|
||||
<textFieldCell key="cell" title="Artist Name" id="ztJ-4E-qvI">
|
||||
<font key="font" metaFont="system" size="16"/>
|
||||
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="FWd-vZ-5CT">
|
||||
<rect key="frame" x="31" y="243" width="300" height="300"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="300" id="69o-NQ-qL7"/>
|
||||
<constraint firstAttribute="width" constant="300" id="8XY-bQ-C7X"/>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="300" id="cyo-wr-hV8"/>
|
||||
</constraints>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyUpOrDown" image="defaultCoverArt" id="scE-kj-gex"/>
|
||||
</imageView>
|
||||
<button verticalHuggingPriority="750" imageHugsTitle="YES" translatesAutoresizingMaskIntoConstraints="NO" id="jMU-bv-TNF">
|
||||
<rect key="frame" x="31" y="184" width="119" height="35"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="33" id="2uQ-mC-4QY"/>
|
||||
<constraint firstAttribute="width" constant="119" id="h2n-ZB-Ufr"/>
|
||||
</constraints>
|
||||
<buttonCell key="cell" type="smallSquare" title="Play Album" bezelStyle="smallSquare" image="playButton" imagePosition="left" alignment="center" lineBreakMode="truncatingTail" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Rtg-Zd-JYc">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="playAlbum:" target="-2" id="LTw-Lg-yH2"/>
|
||||
</connections>
|
||||
</button>
|
||||
<scrollView horizontalCompressionResistancePriority="250" borderType="none" autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" hasHorizontalScroller="NO" horizontalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="BOb-Lr-10M">
|
||||
<rect key="frame" x="359" y="33" width="444" height="425"/>
|
||||
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="9QN-UB-b4l">
|
||||
<rect key="frame" x="0.0" y="0.0" width="444" height="425"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="none" columnSelection="YES" multipleSelection="NO" autosaveColumns="NO" rowSizeStyle="automatic" viewBased="YES" id="ehr-qh-87Q" customClass="AlbumDetailSongListView" customModule="Persephone" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="444" height="425"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<size key="intercellSpacing" width="3" height="2"/>
|
||||
<color key="backgroundColor" red="0.11764705882352941" green="0.11764705882352941" blue="0.11764705882352941" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
|
||||
<tableColumns>
|
||||
<tableColumn identifier="trackNumberColumn" width="40" minWidth="40" maxWidth="40" id="cwb-jE-CEP">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Track No.">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<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="7b7-6s-u1U">
|
||||
<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" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView identifier="trackNumberCell" id="bVN-zt-KW7">
|
||||
<rect key="frame" x="1" y="1" width="40" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="6eU-Jx-HDR">
|
||||
<rect key="frame" x="0.0" y="0.0" width="40" height="17"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" alignment="left" title="1." id="Z5y-oS-Qm8">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="6eU-Jx-HDR" firstAttribute="centerY" secondItem="bVN-zt-KW7" secondAttribute="centerY" id="5cb-M0-OIZ"/>
|
||||
<constraint firstItem="6eU-Jx-HDR" firstAttribute="leading" secondItem="bVN-zt-KW7" secondAttribute="leading" constant="2" id="KXb-Ua-LaU"/>
|
||||
<constraint firstItem="6eU-Jx-HDR" firstAttribute="centerX" secondItem="bVN-zt-KW7" secondAttribute="centerX" id="MGU-H7-mAj"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<outlet property="textField" destination="6eU-Jx-HDR" id="DWy-vj-9Eq"/>
|
||||
</connections>
|
||||
</tableCellView>
|
||||
<tableCellView identifier="discNumberCell" id="MHh-8c-iyL">
|
||||
<rect key="frame" x="1" y="20" width="40" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="nwx-zY-r5o">
|
||||
<rect key="frame" x="0.0" y="0.0" width="441" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="437" id="irN-AG-Pcj"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Disc 1" id="jya-2a-lit">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="nwx-zY-r5o" firstAttribute="centerY" secondItem="MHh-8c-iyL" secondAttribute="centerY" id="IOV-5t-KTN"/>
|
||||
<constraint firstItem="nwx-zY-r5o" firstAttribute="leading" secondItem="MHh-8c-iyL" secondAttribute="leading" constant="2" id="nw0-ya-Pwt"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<outlet property="textField" destination="nwx-zY-r5o" id="NFl-7o-wmK"/>
|
||||
</connections>
|
||||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
</tableColumn>
|
||||
<tableColumn identifier="trackTitleColumn" width="353" minWidth="40" maxWidth="1000" id="7yp-QQ-EzC">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Song Title">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<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="wRS-GW-ubu">
|
||||
<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" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView identifier="songTitleCell" id="41U-5i-Oot">
|
||||
<rect key="frame" x="44" y="1" width="353" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="R8t-bV-9LI">
|
||||
<rect key="frame" x="0.0" y="0.0" width="353" height="17"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" alignment="left" title="My Song Title" id="Sdi-jJ-EOM">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="R8t-bV-9LI" firstAttribute="centerX" secondItem="41U-5i-Oot" secondAttribute="centerX" id="0Hz-cI-Y32"/>
|
||||
<constraint firstItem="R8t-bV-9LI" firstAttribute="centerY" secondItem="41U-5i-Oot" secondAttribute="centerY" id="7iv-Qw-o7d"/>
|
||||
<constraint firstItem="R8t-bV-9LI" firstAttribute="leading" secondItem="41U-5i-Oot" secondAttribute="leading" constant="2" id="UzY-kH-q4q"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<outlet property="textField" destination="R8t-bV-9LI" id="b79-el-ZAY"/>
|
||||
</connections>
|
||||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
</tableColumn>
|
||||
<tableColumn identifier="trackDurationColumn" width="42" minWidth="42" maxWidth="42" id="ha5-ff-2az">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Duration">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<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="mxh-1M-IMh">
|
||||
<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" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView identifier="songDurationCell" id="Lbx-5u-OFw">
|
||||
<rect key="frame" x="400" y="1" width="42" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="pCr-f1-wNs">
|
||||
<rect key="frame" x="0.0" y="0.0" width="42" height="17"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" alignment="right" title="0:00" id="Qe2-WO-eXr">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="pCr-f1-wNs" firstAttribute="leading" secondItem="Lbx-5u-OFw" secondAttribute="leading" constant="2" id="DDY-eP-HQ2"/>
|
||||
<constraint firstItem="pCr-f1-wNs" firstAttribute="centerY" secondItem="Lbx-5u-OFw" secondAttribute="centerY" id="M35-oF-zNB"/>
|
||||
<constraint firstItem="pCr-f1-wNs" firstAttribute="centerX" secondItem="Lbx-5u-OFw" secondAttribute="centerX" id="j50-OZ-wXu"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<outlet property="textField" destination="pCr-f1-wNs" id="r9M-8L-FoO"/>
|
||||
</connections>
|
||||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
</tableColumn>
|
||||
</tableColumns>
|
||||
<connections>
|
||||
<action trigger="doubleAction" selector="playSong:" target="-2" id="HmG-Nf-n4c"/>
|
||||
<outlet property="menu" destination="qbK-4f-3fG" id="gAm-fN-NcP"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
</subviews>
|
||||
<nil key="backgroundColor"/>
|
||||
</clipView>
|
||||
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="w9p-lE-zXP">
|
||||
<rect key="frame" x="-100" y="-100" width="444" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="n7u-af-H0a">
|
||||
<rect key="frame" x="-100" y="-100" width="15" height="102"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="FWd-vZ-5CT" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="31" id="694-aS-G4N"/>
|
||||
<constraint firstItem="jMU-bv-TNF" firstAttribute="leading" secondItem="FWd-vZ-5CT" secondAttribute="leading" id="AwZ-M4-fep"/>
|
||||
<constraint firstItem="BOb-Lr-10M" firstAttribute="leading" secondItem="4Jx-I5-Nkv" secondAttribute="leading" id="GEK-R3-Sw6"/>
|
||||
<constraint firstItem="BOb-Lr-10M" firstAttribute="top" secondItem="4Jx-I5-Nkv" secondAttribute="bottom" constant="29" id="K5g-Kd-iHK"/>
|
||||
<constraint firstAttribute="bottom" secondItem="BOb-Lr-10M" secondAttribute="bottom" constant="33" id="MZ6-81-2qe"/>
|
||||
<constraint firstItem="4Jx-I5-Nkv" firstAttribute="leading" secondItem="m2v-pR-e9v" secondAttribute="leading" id="NaJ-VT-uln"/>
|
||||
<constraint firstItem="FWd-vZ-5CT" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" constant="25" id="QA9-xn-fzY"/>
|
||||
<constraint firstItem="m2v-pR-e9v" firstAttribute="top" secondItem="FWd-vZ-5CT" secondAttribute="top" id="bqi-HD-KZW"/>
|
||||
<constraint firstItem="jMU-bv-TNF" firstAttribute="top" secondItem="FWd-vZ-5CT" secondAttribute="bottom" constant="25" id="dd1-6b-TEN"/>
|
||||
<constraint firstItem="4Jx-I5-Nkv" firstAttribute="trailing" secondItem="m2v-pR-e9v" secondAttribute="trailing" id="dmh-TC-Ncr"/>
|
||||
<constraint firstItem="m2v-pR-e9v" firstAttribute="leading" secondItem="FWd-vZ-5CT" secondAttribute="trailing" constant="28" id="icS-vq-PkK"/>
|
||||
<constraint firstItem="4Jx-I5-Nkv" firstAttribute="top" secondItem="m2v-pR-e9v" secondAttribute="bottom" constant="8" symbolic="YES" id="nTZ-Ew-sDQ"/>
|
||||
<constraint firstAttribute="trailing" secondItem="m2v-pR-e9v" secondAttribute="trailing" constant="20" symbolic="YES" id="qyi-X9-6B9"/>
|
||||
<constraint firstItem="BOb-Lr-10M" firstAttribute="trailing" secondItem="4Jx-I5-Nkv" secondAttribute="trailing" id="sPx-cY-MeX"/>
|
||||
</constraints>
|
||||
<point key="canvasLocation" x="262.5" y="121"/>
|
||||
</customView>
|
||||
<menu id="qbK-4f-3fG">
|
||||
<items>
|
||||
<menuItem title="Play Song" id="poo-OI-Kwi">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="menuActionPlaySong:" target="-2" id="ZB9-dq-reF"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Add Song to Queue" id="PdP-4s-xfR">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="menuActionAppendSong:" target="-2" id="C0J-2v-bf4"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
<point key="canvasLocation" x="262" y="-243"/>
|
||||
</menu>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="defaultCoverArt" width="128" height="128"/>
|
||||
<image name="playButton" width="17" height="17"/>
|
||||
</resources>
|
||||
</document>
|
||||
@ -37,42 +37,39 @@
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<imageView identifier="albumArtwork" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="Kfb-8f-ean">
|
||||
<imageView identifier="albumArtwork" horizontalHuggingPriority="750" verticalHuggingPriority="750" placeholderIntrinsicWidth="128" placeholderIntrinsicHeight="128" translatesAutoresizingMaskIntoConstraints="NO" id="Kfb-8f-ean">
|
||||
<rect key="frame" x="0.0" y="39" width="128" height="128"/>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyUpOrDown" image="defaultCoverArt" 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">
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="x5e-56-uVO" userLabel="Album Detail Button">
|
||||
<rect key="frame" x="0.0" y="39" width="128" height="128"/>
|
||||
<buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" alignment="center" lineBreakMode="truncatingTail" state="on" imageScaling="proportionallyDown" inset="2" id="MTh-fn-aCH">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="smallSystemBold"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="playAlbum:" target="-2" id="gNt-Rn-kte"/>
|
||||
<action selector="showAlbumDetail:" target="-2" id="nO1-4H-LHS"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="x5e-56-uVO" firstAttribute="leading" secondItem="Kfb-8f-ean" secondAttribute="leading" id="1Hi-Uk-rkL"/>
|
||||
<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="x5e-56-uVO" firstAttribute="trailing" secondItem="Kfb-8f-ean" secondAttribute="trailing" id="BYd-Fg-DVb"/>
|
||||
<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 firstItem="x5e-56-uVO" firstAttribute="top" secondItem="Kfb-8f-ean" secondAttribute="top" id="hw2-ik-6VW"/>
|
||||
<constraint firstItem="x5e-56-uVO" firstAttribute="bottom" secondItem="Kfb-8f-ean" secondAttribute="bottom" id="iVQ-Vn-dSV"/>
|
||||
<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>
|
||||
@ -80,6 +77,5 @@
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="defaultCoverArt" width="128" height="128"/>
|
||||
<image name="playButtonLarge" width="22" height="22"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@ -569,8 +569,8 @@
|
||||
<rect key="frame" x="1" y="1" width="200" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="xgd-Cz-np3">
|
||||
<rect key="frame" x="0.0" y="2" width="447" height="14"/>
|
||||
<textField verticalHuggingPriority="750" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="xgd-Cz-np3">
|
||||
<rect key="frame" x="0.0" y="2" width="322" height="14"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="443" id="mkA-ng-q8a"/>
|
||||
</constraints>
|
||||
|
||||
@ -16,6 +16,10 @@ struct MPDStopAction: Action {}
|
||||
struct MPDNextTrackAction: Action {}
|
||||
struct MPDPrevTrackAction: Action {}
|
||||
|
||||
struct MPDAppendTrack: Action {
|
||||
let song: MPDClient.MPDSong
|
||||
}
|
||||
|
||||
struct MPDPlayTrack: Action {
|
||||
let queuePos: Int
|
||||
}
|
||||
|
||||
@ -30,6 +30,9 @@ func mpdReducer(action: Action, state: MPDState?) -> MPDState {
|
||||
case is MPDPrevTrackAction:
|
||||
App.mpdClient.prevTrack()
|
||||
|
||||
case let action as MPDAppendTrack:
|
||||
App.mpdClient.appendSong(action.song)
|
||||
|
||||
case let action as MPDPlayTrack:
|
||||
App.mpdClient.playTrack(at: action.queuePos)
|
||||
|
||||
|
||||
13
Persephone/Views/AlbumDetailSongListView.swift
Normal file
13
Persephone/Views/AlbumDetailSongListView.swift
Normal file
@ -0,0 +1,13 @@
|
||||
//
|
||||
// AlbumDetailSongListView.swift
|
||||
// Persephone
|
||||
//
|
||||
// Created by Daniel Barber on 2019/6/02.
|
||||
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
|
||||
class AlbumDetailSongListView: NSTableView {
|
||||
override func drawGrid(inClipRect clipRect: NSRect) { }
|
||||
}
|
||||
27
Persephone/Views/AlbumDetailSongRowView.swift
Normal file
27
Persephone/Views/AlbumDetailSongRowView.swift
Normal file
@ -0,0 +1,27 @@
|
||||
//
|
||||
// AlbumDetailSongRowView.swift
|
||||
// Persephone
|
||||
//
|
||||
// Created by Daniel Barber on 2019/5/22.
|
||||
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
|
||||
class AlbumDetailSongRowView: NSTableRowView {
|
||||
override func drawBackground(in dirtyRect: NSRect) {
|
||||
let borderPath = CGMutablePath()
|
||||
let startingPoint = CGPoint(x: dirtyRect.origin.x, y: dirtyRect.height)
|
||||
let endingPoint = CGPoint(x: dirtyRect.width, y: dirtyRect.height)
|
||||
|
||||
borderPath.move(to: startingPoint)
|
||||
borderPath.addLine(to: endingPoint)
|
||||
|
||||
let shapeLayer = CAShapeLayer()
|
||||
self.layer?.addSublayer(shapeLayer)
|
||||
|
||||
shapeLayer.path = borderPath
|
||||
shapeLayer.strokeColor = NSColor.secondaryLabelColor.cgColor
|
||||
shapeLayer.lineWidth = 1
|
||||
}
|
||||
}
|
||||
@ -9,80 +9,9 @@
|
||||
import AppKit
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(viewDidScroll(_:)),
|
||||
name: NSScrollView.didLiveScrollNotification,
|
||||
object: nil
|
||||
)
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
|
||||
hidePlayButton()
|
||||
}
|
||||
|
||||
@objc func viewWillScroll(_ notification: Notification) {
|
||||
hidePlayButton()
|
||||
}
|
||||
|
||||
@objc func viewDidScroll(_ 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!
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user