mirror of
https://github.com/danbee/persephone
synced 2025-03-04 08:39:11 +00:00
Compare commits
4 Commits
4c14ed5078
...
52fce2c212
| Author | SHA1 | Date | |
|---|---|---|---|
| 52fce2c212 | |||
| e1dc7e3d99 | |||
| d8f762b3c4 | |||
| b241c3e5dd |
@ -111,6 +111,7 @@
|
||||
E4B11BC22275EE410075461B /* AlbumListActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11BC12275EE410075461B /* AlbumListActions.swift */; };
|
||||
E4C8B53C22342005009A20F3 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */; };
|
||||
E4C8B53E22349002009A20F3 /* MPDIdle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C8B53D22349002009A20F3 /* MPDIdle.swift */; };
|
||||
E4E7A6AD22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E7A6AC22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift */; };
|
||||
E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC8F2204EC7F0024217A /* Delegate.swift */; };
|
||||
E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC912204F4B80024217A /* QueueViewController.swift */; };
|
||||
E4E8CC9A22075D370024217A /* MPDSong.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC9922075D370024217A /* MPDSong.swift */; };
|
||||
@ -314,6 +315,7 @@
|
||||
E4B11BC12275EE410075461B /* AlbumListActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumListActions.swift; sourceTree = "<group>"; };
|
||||
E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; };
|
||||
E4C8B53D22349002009A20F3 /* MPDIdle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDIdle.swift; sourceTree = "<group>"; };
|
||||
E4E7A6AC22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AlbumDetailView+NSTableViewDelegate.swift"; sourceTree = "<group>"; };
|
||||
E4E8CC8F2204EC7F0024217A /* Delegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Delegate.swift; sourceTree = "<group>"; };
|
||||
E4E8CC912204F4B80024217A /* QueueViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueViewController.swift; sourceTree = "<group>"; };
|
||||
E4E8CC9922075D370024217A /* MPDSong.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDSong.swift; sourceTree = "<group>"; };
|
||||
@ -683,6 +685,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E43B67A822909793007DCF55 /* AlbumDetailView.swift */,
|
||||
E4E7A6AC22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift */,
|
||||
E408D3C1220E134F0006D9BE /* AlbumViewController.swift */,
|
||||
E47E2FD4222071FD00F747E6 /* AlbumViewItem.swift */,
|
||||
E47E2FD022205C4600F747E6 /* MainSplitViewController.swift */,
|
||||
@ -940,6 +943,7 @@
|
||||
E4EB2379220F10B8008C70C0 /* MPDPair.swift in Sources */,
|
||||
E440519E227BB0720090CD6F /* UIReducer.swift in Sources */,
|
||||
E43B67AA22909793007DCF55 /* AlbumDetailView.swift in Sources */,
|
||||
E4E7A6AD22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift in Sources */,
|
||||
E4FF7190227601B400D4C412 /* PreferencesReducer.swift in Sources */,
|
||||
E4F6B463221E125900ACF42A /* QueueItem.swift in Sources */,
|
||||
E4A83BF12221FAA00098FED6 /* PreferencesViewController.swift in Sources */,
|
||||
|
||||
@ -16,6 +16,11 @@ class AppDelegate: NSObject,
|
||||
MediaKeyTapDelegate {
|
||||
var mediaKeyTap: MediaKeyTap?
|
||||
|
||||
@IBOutlet weak var mainWindowMenuItem: NSMenuItem!
|
||||
@IBOutlet weak var updateDatabaseMenuItem: NSMenuItem!
|
||||
@IBOutlet weak var playSelectedSongMenuItem: NSMenuItem!
|
||||
@IBOutlet weak var addSelectedSongToQueueMenuItem: NSMenuItem!
|
||||
|
||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||
App.mpdServerController.connect()
|
||||
instantiateUserNotificationsController()
|
||||
@ -97,6 +102,11 @@ class AppDelegate: NSObject,
|
||||
}
|
||||
}
|
||||
|
||||
func setSongMenuItemsState(selectedSong: Song?) {
|
||||
playSelectedSongMenuItem.isEnabled = selectedSong != nil
|
||||
addSelectedSongToQueueMenuItem.isEnabled = selectedSong != nil
|
||||
}
|
||||
|
||||
func handle(mediaKey: MediaKey, event: KeyEvent) {
|
||||
switch mediaKey {
|
||||
case .playPause:
|
||||
@ -125,8 +135,20 @@ class AppDelegate: NSObject,
|
||||
App.store.dispatch(MPDPrevTrackAction())
|
||||
}
|
||||
|
||||
@IBOutlet weak var mainWindowMenuItem: NSMenuItem!
|
||||
@IBOutlet weak var updateDatabaseMenuItem: NSMenuItem!
|
||||
@IBAction func playSelectedSongAction(_ sender: NSMenuItem) {
|
||||
guard let song = App.store.state.uiState.selectedSong
|
||||
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 addSelectedSongToQueueAction(_ sender: NSMenuItem) {
|
||||
guard let song = App.store.state.uiState.selectedSong
|
||||
else { return }
|
||||
|
||||
App.store.dispatch(MPDAppendTrack(song: song.mpdSong))
|
||||
}
|
||||
}
|
||||
|
||||
extension AppDelegate: StoreSubscriber {
|
||||
@ -135,5 +157,6 @@ extension AppDelegate: StoreSubscriber {
|
||||
func newState(state: UIState) {
|
||||
updateDatabaseMenuItem.isEnabled = !state.databaseUpdating
|
||||
setMainWindowStateMenuItem(state: state.mainWindowState)
|
||||
setSongMenuItemsState(selectedSong: state.selectedSong)
|
||||
}
|
||||
}
|
||||
|
||||
102
Persephone/Controllers/AlbumDetailView+NSTableViewDelegate.swift
Normal file
102
Persephone/Controllers/AlbumDetailView+NSTableViewDelegate.swift
Normal file
@ -0,0 +1,102 @@
|
||||
//
|
||||
// AlbumDetailView+NSTableViewDelegate.swift
|
||||
// Persephone
|
||||
//
|
||||
// Created by Daniel Barber on 2019/6/07.
|
||||
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
|
||||
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 tableViewSelectionDidChange(_ notification: Notification) {
|
||||
guard let tableView = notification.object as? NSTableView
|
||||
else { return }
|
||||
|
||||
if tableView.selectedRow >= 0 {
|
||||
let song = dataSource.albumSongs[tableView.selectedRow].song
|
||||
|
||||
App.store.dispatch(SetSelectedSong(selectedSong: song))
|
||||
} else {
|
||||
App.store.dispatch(SetSelectedSong(selectedSong: 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
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,7 @@
|
||||
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import AppKit
|
||||
|
||||
class AlbumDetailView: NSViewController {
|
||||
var album: Album?
|
||||
@ -27,6 +27,7 @@ class AlbumDetailView: NSViewController {
|
||||
albumTracksView.delegate = self
|
||||
albumTracksView.intercellSpacing = CGSize(width: 0, height: 18)
|
||||
albumTracksView.floatsGroupRows = false
|
||||
albumTracksView.columnAutoresizingStyle = .sequentialColumnAutoresizingStyle
|
||||
|
||||
albumCoverView.wantsLayer = true
|
||||
albumCoverView.layer?.cornerRadius = 5
|
||||
@ -59,6 +60,39 @@ class AlbumDetailView: NSViewController {
|
||||
albumTitle.stringValue = ""
|
||||
albumArtist.stringValue = ""
|
||||
albumCoverView.image = .defaultCoverArt
|
||||
|
||||
App.store.dispatch(SetSelectedSong(selectedSong: nil))
|
||||
}
|
||||
|
||||
@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) {
|
||||
@ -104,79 +138,3 @@ class AlbumDetailView: NSViewController {
|
||||
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 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
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,7 +11,6 @@ import AppKit
|
||||
class AlbumViewItem: NSCollectionViewItem {
|
||||
var observer: NSKeyValueObservation?
|
||||
var album: Album?
|
||||
var detailPopover: NSPopover?
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
@ -31,7 +30,7 @@ class AlbumViewItem: NSCollectionViewItem {
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
|
||||
detailPopover?.close()
|
||||
AlbumDetailView.popover.close()
|
||||
}
|
||||
|
||||
func setAlbum(_ album: Album) {
|
||||
@ -58,12 +57,6 @@ class AlbumViewItem: NSCollectionViewItem {
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func playAlbum(_ sender: NSButton) {
|
||||
guard let album = album else { return }
|
||||
|
||||
App.store.dispatch(MPDPlayAlbum(album: album.mpdAlbum))
|
||||
}
|
||||
|
||||
@IBAction func showAlbumDetail(_ sender: NSButton) {
|
||||
guard let album = album else { return }
|
||||
|
||||
|
||||
@ -58,6 +58,10 @@ extension MPDClient {
|
||||
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:
|
||||
|
||||
@ -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))
|
||||
}
|
||||
@ -40,4 +44,8 @@ extension MPDClient {
|
||||
}
|
||||
mpd_run_play_pos(self.connection, 0)
|
||||
}
|
||||
|
||||
func sendAppendSong(_ song: MPDSong) {
|
||||
mpd_run_add(self.connection, song.uri)
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,6 +30,7 @@ extension MPDClient {
|
||||
case fetchQueue
|
||||
case playTrack
|
||||
case replaceQueue
|
||||
case appendSong
|
||||
|
||||
// Album commands
|
||||
case fetchAllAlbums
|
||||
|
||||
@ -18,11 +18,11 @@
|
||||
<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="561"/>
|
||||
<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="507" width="448" height="29"/>
|
||||
<rect key="frame" x="357" y="514" width="448" height="29"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="444" id="erC-QS-9hc"/>
|
||||
</constraints>
|
||||
@ -33,7 +33,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="4Jx-I5-Nkv">
|
||||
<rect key="frame" x="357" y="480" width="448" height="19"/>
|
||||
<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"/>
|
||||
@ -41,7 +41,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="FWd-vZ-5CT">
|
||||
<rect key="frame" x="31" y="236" width="300" height="300"/>
|
||||
<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"/>
|
||||
@ -50,7 +50,7 @@
|
||||
<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="177" width="119" height="35"/>
|
||||
<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"/>
|
||||
@ -59,15 +59,18 @@
|
||||
<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 borderType="none" autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="BOb-Lr-10M">
|
||||
<rect key="frame" x="359" y="33" width="444" height="418"/>
|
||||
<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="418"/>
|
||||
<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="418"/>
|
||||
<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"/>
|
||||
@ -171,7 +174,7 @@
|
||||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
</tableColumn>
|
||||
<tableColumn identifier="trackDurationColumn" width="42" minWidth="40" maxWidth="1000" id="ha5-ff-2az">
|
||||
<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"/>
|
||||
@ -209,16 +212,20 @@
|
||||
</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="0.0" y="420" width="444" height="16"/>
|
||||
<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="224" y="17" width="15" height="102"/>
|
||||
<rect key="frame" x="-100" y="-100" width="15" height="102"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
@ -239,8 +246,25 @@
|
||||
<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="117.5"/>
|
||||
<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"/>
|
||||
|
||||
@ -51,20 +51,6 @@
|
||||
<action selector="showAlbumDetail:" target="-2" id="nO1-4H-LHS"/>
|
||||
</connections>
|
||||
</button>
|
||||
<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="x5e-56-uVO" firstAttribute="leading" secondItem="Kfb-8f-ean" secondAttribute="leading" id="1Hi-Uk-rkL"/>
|
||||
@ -73,7 +59,6 @@
|
||||
<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="Kfb-8f-ean" 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"/>
|
||||
@ -81,12 +66,10 @@
|
||||
<constraint firstAttribute="bottom" secondItem="5Uu-j1-qyT" secondAttribute="bottom" id="gci-4h-pDZ"/>
|
||||
<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 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>
|
||||
@ -94,6 +77,5 @@
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="defaultCoverArt" width="128" height="128"/>
|
||||
<image name="playButtonLarge" width="22" height="22"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@ -75,6 +75,24 @@
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Song" id="elk-xW-VXb">
|
||||
<menu key="submenu" title="Song" autoenablesItems="NO" id="RuT-kk-xTu">
|
||||
<items>
|
||||
<menuItem title="Play selected song" enabled="NO" id="dyT-9E-DRY">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="playSelectedSongAction:" target="Voe-Tx-rLC" id="jIo-ux-Mhr"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Add selected song to queue" enabled="NO" id="JFH-jT-sBp">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="addSelectedSongToQueueAction:" target="Voe-Tx-rLC" id="9j9-Xd-g0D"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Window" id="aUF-d1-5bR">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
|
||||
@ -132,7 +150,9 @@
|
||||
</application>
|
||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Persephone" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="addSelectedSongToQueueMenuItem" destination="JFH-jT-sBp" id="9dy-sJ-XYS"/>
|
||||
<outlet property="mainWindowMenuItem" destination="1Sq-L7-znT" id="dC6-yY-6Ss"/>
|
||||
<outlet property="playSelectedSongMenuItem" destination="dyT-9E-DRY" id="UY2-SN-YMF"/>
|
||||
<outlet property="updateDatabaseMenuItem" destination="EJg-93-1F6" id="gMf-SQ-lyI"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -17,3 +17,7 @@ struct MainWindowDidMinimizeAction: Action {}
|
||||
struct DatabaseUpdateStartedAction: Action {}
|
||||
|
||||
struct DatabaseUpdateFinishedAction: Action {}
|
||||
|
||||
struct SetSelectedSong: Action {
|
||||
let selectedSong: Song?
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -27,6 +27,9 @@ func uiReducer(action: Action, state: UIState?) -> UIState {
|
||||
case is DatabaseUpdateFinishedAction:
|
||||
state.databaseUpdating = false
|
||||
|
||||
case let action as SetSelectedSong:
|
||||
state.selectedSong = action.selectedSong
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
@ -18,4 +18,6 @@ struct UIState: StateType {
|
||||
var mainWindowState: MainWindowState = .closed
|
||||
|
||||
var databaseUpdating: Bool = false
|
||||
|
||||
var selectedSong: Song?
|
||||
}
|
||||
|
||||
@ -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