1
1
mirror of https://github.com/danbee/persephone synced 2025-03-04 08:39:11 +00:00

Compare commits

...

19 Commits

Author SHA1 Message Date
dc60049243
Add convenience initializer to NSPasteboardItem 2019-06-28 14:58:38 -04:00
1af2c939a2
We don't need to instantiate this separately 2019-06-22 13:48:27 -04:00
616880415f
Add and wire up "Play Next" song menu item 2019-06-22 13:48:27 -04:00
97f98e7a40
Add and wire up "Play Next" menu option 2019-06-22 13:48:27 -04:00
5b66ddb5a3
Some spacing tweaks 2019-06-22 13:48:26 -04:00
fa0816ce7c
Dragging song view resizes to fit contents 2019-06-22 13:48:26 -04:00
f687e89f9d
Drag songs with custom drag image 2019-06-22 13:48:26 -04:00
ba64fed16a
Drag album songs onto queue 2019-06-22 13:48:26 -04:00
1bd6edfb33
Turns out Swift can synthesize this! 2019-06-22 13:48:26 -04:00
2ff2e1c96f
Fix playing song not getting updated when moving a track 2019-06-22 13:48:26 -04:00
95323e8a6f
Drag and drop works properly now 2019-06-22 13:48:25 -04:00
aef5b8534b
Drag and drop moves tracks in queue
For some reason we're not seeing the insert indicator, and I can't
figure out why.
2019-06-22 13:48:25 -04:00
8c385d9c88
Only display queue context menu on songs 2019-06-22 13:48:25 -04:00
5416d793ad
Actually clear the queue 2019-06-22 13:48:25 -04:00
b2a80abe7b
Wire up main queue menu options 2019-06-22 13:48:25 -04:00
aecff619a2
Add queue play/remove menu 2019-06-22 13:48:24 -04:00
6fc7e09deb
Backspace to remove track from queue 2019-06-22 13:48:24 -04:00
5a9c2c0698
Context menu to remove tracks from queue 2019-06-22 13:48:24 -04:00
cd094c3a64
Implement top level song menu 2019-06-22 13:48:24 -04:00
37 changed files with 969 additions and 168 deletions

View File

@ -18,6 +18,7 @@
E408D3C2220E134F0006D9BE /* AlbumViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3C1220E134F0006D9BE /* AlbumViewController.swift */; };
E408D3CB220E341D0006D9BE /* AlbumViewItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = E408D3C9220E341D0006D9BE /* AlbumViewItem.xib */; };
E40FE71B221B904300A4223F /* NSEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = E40FE71A221B904300A4223F /* NSEvent.swift */; };
E4120D6C22AD8139004CB1F8 /* QueueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4120D6B22AD8139004CB1F8 /* QueueView.swift */; };
E419E2872249B96600216A8C /* Song.swift in Sources */ = {isa = PBXBuildFile; fileRef = E419E2862249B96600216A8C /* Song.swift */; };
E41B22C621FB932700D544F6 /* MPDClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41B22C521FB932700D544F6 /* MPDClient.swift */; };
E41E52FD223BF87300173814 /* MPDClient+Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E52FC223BF87300173814 /* MPDClient+Connection.swift */; };
@ -39,6 +40,7 @@
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 */; };
E43AC1F122C68E6A001E483C /* NSPasteboardItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43AC1F022C68E6A001E483C /* NSPasteboardItem.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 */; };
@ -56,6 +58,8 @@
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 */; };
E451E36B22BD214D008BE9B2 /* DraggedSongType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E451E36A22BD214D008BE9B2 /* DraggedSongType.swift */; };
E451E36E22BD2501008BE9B2 /* DraggedSong.swift in Sources */ = {isa = PBXBuildFile; fileRef = E451E36C22BD23DB008BE9B2 /* DraggedSong.swift */; };
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 */; };
@ -70,6 +74,10 @@
E47E2FD72220720300F747E6 /* AlbumItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FD62220720300F747E6 /* AlbumItemView.swift */; };
E47E2FDD2220A6D100F747E6 /* Time.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FDC2220A6D100F747E6 /* Time.swift */; };
E47E2FE52220AA0700F747E6 /* AlbumViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FE42220AA0700F747E6 /* AlbumViewLayout.swift */; };
E489E39922B85D0400CA8CBD /* NSPasteboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E489E39822B85D0400CA8CBD /* NSPasteboard.swift */; };
E489E39D22B9CF0000CA8CBD /* NSView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E489E39C22B9CF0000CA8CBD /* NSView.swift */; };
E489E3A422B9D31800CA8CBD /* DraggedSongView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E489E3A222B9D31800CA8CBD /* DraggedSongView.swift */; };
E489E3A522B9D31800CA8CBD /* DraggedSongView.xib in Resources */ = {isa = PBXBuildFile; fileRef = E489E3A322B9D31800CA8CBD /* DraggedSongView.xib */; };
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 */; };
@ -111,6 +119,8 @@
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 */; };
E4D3BFA622B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3BFA522B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.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 */; };
@ -191,6 +201,7 @@
E408D3C1220E134F0006D9BE /* AlbumViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumViewController.swift; sourceTree = "<group>"; };
E408D3C9220E341D0006D9BE /* AlbumViewItem.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AlbumViewItem.xib; sourceTree = "<group>"; };
E40FE71A221B904300A4223F /* NSEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSEvent.swift; sourceTree = "<group>"; };
E4120D6B22AD8139004CB1F8 /* QueueView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueView.swift; sourceTree = "<group>"; };
E419E2862249B96600216A8C /* Song.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Song.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>"; };
E41B22C421FB715A00D544F6 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = "<group>"; };
@ -250,6 +261,7 @@
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>"; };
E43AC1F022C68E6A001E483C /* NSPasteboardItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSPasteboardItem.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>"; };
@ -272,6 +284,8 @@
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>"; };
E451E36A22BD214D008BE9B2 /* DraggedSongType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggedSongType.swift; sourceTree = "<group>"; };
E451E36C22BD23DB008BE9B2 /* DraggedSong.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggedSong.swift; 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; };
@ -287,6 +301,10 @@
E47E2FD62220720300F747E6 /* AlbumItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumItemView.swift; sourceTree = "<group>"; };
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>"; };
E489E39822B85D0400CA8CBD /* NSPasteboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSPasteboard.swift; sourceTree = "<group>"; };
E489E39C22B9CF0000CA8CBD /* NSView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSView.swift; sourceTree = "<group>"; };
E489E3A222B9D31800CA8CBD /* DraggedSongView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggedSongView.swift; sourceTree = "<group>"; };
E489E3A322B9D31800CA8CBD /* DraggedSongView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DraggedSongView.xib; 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>"; };
@ -314,6 +332,8 @@
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>"; };
E4D3BFA522B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QueueViewController+NSOutlineViewDelegate.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>"; };
@ -440,6 +460,9 @@
E435E3E1221CD4E200184CFC /* NSFont.swift */,
E435E3E3221CD75D00184CFC /* NSImage.swift */,
E408D3B8220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift */,
E489E39822B85D0400CA8CBD /* NSPasteboard.swift */,
E489E39C22B9CF0000CA8CBD /* NSView.swift */,
E43AC1F022C68E6A001E483C /* NSPasteboardItem.swift */,
);
path = Extensions;
sourceTree = "<group>";
@ -464,12 +487,14 @@
E408D3C3220E138B0006D9BE /* Views */ = {
isa = PBXGroup;
children = (
E47E2FD62220720300F747E6 /* AlbumItemView.swift */,
E47E2FD222205D2500F747E6 /* MainWindow.swift */,
E4B11BB7227538FA0075461B /* CurrentCoverArtView.swift */,
E423563F228623D2001216D6 /* QueueSongTitleView.swift */,
E45878372296173C00586A1C /* AlbumDetailSongRowView.swift */,
E4A3A6A022A457B600EA2C40 /* AlbumDetailSongListView.swift */,
E45878372296173C00586A1C /* AlbumDetailSongRowView.swift */,
E47E2FD62220720300F747E6 /* AlbumItemView.swift */,
E4B11BB7227538FA0075461B /* CurrentCoverArtView.swift */,
E489E3A222B9D31800CA8CBD /* DraggedSongView.swift */,
E47E2FD222205D2500F747E6 /* MainWindow.swift */,
E423563F228623D2001216D6 /* QueueSongTitleView.swift */,
E4120D6B22AD8139004CB1F8 /* QueueView.swift */,
);
path = Views;
sourceTree = "<group>";
@ -683,11 +708,13 @@
isa = PBXGroup;
children = (
E43B67A822909793007DCF55 /* AlbumDetailView.swift */,
E4E7A6AC22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift */,
E408D3C1220E134F0006D9BE /* AlbumViewController.swift */,
E47E2FD4222071FD00F747E6 /* AlbumViewItem.swift */,
E47E2FD022205C4600F747E6 /* MainSplitViewController.swift */,
E4405191227644340090CD6F /* MPDServerController.swift */,
E4E8CC912204F4B80024217A /* QueueViewController.swift */,
E4D3BFA522B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift */,
E4B11BB52275374B0075461B /* UserNotificationsController.swift */,
E465049921E94DF500A70F4C /* WindowController.swift */,
);
@ -697,9 +724,10 @@
E4D1B598220BA3C90026F233 /* Resources */ = {
isa = PBXGroup;
children = (
E40786212110CE70006887B1 /* Main.storyboard */,
E43B67A922909793007DCF55 /* AlbumDetailView.xib */,
E408D3C9220E341D0006D9BE /* AlbumViewItem.xib */,
E489E3A322B9D31800CA8CBD /* DraggedSongView.xib */,
E40786212110CE70006887B1 /* Main.storyboard */,
);
path = Resources;
sourceTree = "<group>";
@ -724,6 +752,8 @@
E419E2862249B96600216A8C /* Song.swift */,
E47E2FDC2220A6D100F747E6 /* Time.swift */,
E4B11B72226A6C770075461B /* TrackTimer.swift */,
E451E36A22BD214D008BE9B2 /* DraggedSongType.swift */,
E451E36C22BD23DB008BE9B2 /* DraggedSong.swift */,
);
path = Models;
sourceTree = "<group>";
@ -853,6 +883,7 @@
E43B67AB22909793007DCF55 /* AlbumDetailView.xib in Resources */,
E45E4FDC22515D87004B537F /* Cartfile in Resources */,
E450AD98222633920091BED3 /* Alamofire.framework.dSYM in Resources */,
E489E3A522B9D31800CA8CBD /* DraggedSongView.xib in Resources */,
E40786202110CE70006887B1 /* Assets.xcassets in Resources */,
E45E4FDF225168DA004B537F /* CryptoSwift.framework.dSYM in Resources */,
E42A8F3C22176D6400A13ED9 /* README.md in Resources */,
@ -915,6 +946,7 @@
E4B11BB62275374B0075461B /* UserNotificationsController.swift in Sources */,
E4B11B68226A4FA00075461B /* QueueState.swift in Sources */,
E4928E0B2218D62A001D4BEA /* CGColor.swift in Sources */,
E4D3BFA622B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift in Sources */,
E4405196227879960090CD6F /* MPDActions.swift in Sources */,
E4405192227644340090CD6F /* MPDServerController.swift in Sources */,
E4C8B53E22349002009A20F3 /* MPDIdle.swift in Sources */,
@ -927,6 +959,7 @@
E440519822787CB40090CD6F /* MPDState.swift in Sources */,
E42410B62241B956005ED6DF /* MPDClient+Database.swift in Sources */,
E4235640228623D2001216D6 /* QueueSongTitleView.swift in Sources */,
E451E36E22BD2501008BE9B2 /* DraggedSong.swift in Sources */,
E4A642DA22090CBE00067D21 /* MPDStatus.swift in Sources */,
E4B11BC02275EE150075461B /* QueueActions.swift in Sources */,
E47E2FD72220720300F747E6 /* AlbumItemView.swift in Sources */,
@ -938,18 +971,23 @@
E45962C62241A78500FC1A1E /* MPDCommand.swift in Sources */,
E408D3B9220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift in Sources */,
E4EB2379220F10B8008C70C0 /* MPDPair.swift in Sources */,
E489E3A422B9D31800CA8CBD /* DraggedSongView.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 */,
E4B11BC22275EE410075461B /* AlbumListActions.swift in Sources */,
E4B11B61226A4C000075461B /* PlayerReducer.swift in Sources */,
E4FF71922276029000D4C412 /* PreferencesActions.swift in Sources */,
E489E39922B85D0400CA8CBD /* NSPasteboard.swift in Sources */,
E43AC1F122C68E6A001E483C /* NSPasteboardItem.swift in Sources */,
E465049A21E94DF500A70F4C /* WindowController.swift in Sources */,
E41B22C621FB932700D544F6 /* MPDClient.swift in Sources */,
E47E2FDD2220A6D100F747E6 /* Time.swift in Sources */,
E419E2872249B96600216A8C /* Song.swift in Sources */,
E451E36B22BD214D008BE9B2 /* DraggedSongType.swift in Sources */,
E44051A0227BB0AB0090CD6F /* UIState.swift in Sources */,
E4FF718E2276010E00D4C412 /* PreferencesState.swift in Sources */,
E439109822640213002982E9 /* SongNotifierService.swift in Sources */,
@ -965,6 +1003,7 @@
E47E2FD122205C4600F747E6 /* MainSplitViewController.swift in Sources */,
E4B11BB8227538FA0075461B /* CurrentCoverArtView.swift in Sources */,
E4E8CC9A22075D370024217A /* MPDSong.swift in Sources */,
E4120D6C22AD8139004CB1F8 /* QueueView.swift in Sources */,
E41EA46C221636AF0068EF46 /* GeneralPrefsViewController.swift in Sources */,
E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */,
E4A83BF4222207D50098FED6 /* CoverArtService.swift in Sources */,
@ -988,6 +1027,7 @@
E4B11B6A226A4FBC0075461B /* AlbumListState.swift in Sources */,
E41E5305223BFB0700173814 /* MPDClient+Error.swift in Sources */,
E435E3E2221CD4E200184CFC /* NSFont.swift in Sources */,
E489E39D22B9CF0000CA8CBD /* NSView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -16,6 +16,12 @@ 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 playSelectedSongNextMenuItem: NSMenuItem!
@IBOutlet weak var addSelectedSongToQueueMenuItem: NSMenuItem!
func applicationDidFinishLaunching(_ aNotification: Notification) {
App.mpdServerController.connect()
instantiateUserNotificationsController()
@ -97,6 +103,12 @@ class AppDelegate: NSObject,
}
}
func setSongMenuItemsState(selectedSong: Song?) {
playSelectedSongMenuItem.isEnabled = selectedSong != nil
playSelectedSongNextMenuItem.isEnabled = selectedSong != nil
addSelectedSongToQueueMenuItem.isEnabled = selectedSong != nil
}
func handle(mediaKey: MediaKey, event: KeyEvent) {
switch mediaKey {
case .playPause:
@ -125,8 +137,53 @@ class AppDelegate: NSObject,
App.store.dispatch(MPDPrevTrackAction())
}
@IBOutlet weak var mainWindowMenuItem: NSMenuItem!
@IBOutlet weak var updateDatabaseMenuItem: NSMenuItem!
@IBAction func removeQueueSongMenuAction(_ sender: NSMenuItem) {
guard let queueItem = App.store.state.uiState.selectedQueueItem
else { return }
App.store.dispatch(MPDRemoveTrack(queuePos: queueItem.queuePos))
App.store.dispatch(SetSelectedQueueItem(selectedQueueItem: nil))
}
@IBAction func clearQueueMenuAction(_ sender: NSMenuItem) {
let alert = NSAlert()
alert.alertStyle = .warning
alert.messageText = "Are you sure you want to clear the queue?"
alert.informativeText = "You cant undo this action."
alert.addButton(withTitle: "Clear Queue")
alert.addButton(withTitle: "Cancel")
let result = alert.runModal()
if result == .alertFirstButtonReturn {
App.store.dispatch(MPDClearQueue())
}
}
@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 playSelectedSongNextAction(_ sender: NSMenuItem) {
let queuePos = App.store.state.queueState.queuePos
guard let song = App.store.state.uiState.selectedSong,
queuePos > -1
else { return }
App.store.dispatch(
MPDAddSongToQueue(songUri: song.mpdSong.uriString, queuePos: queuePos + 1)
)
}
@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 +192,6 @@ extension AppDelegate: StoreSubscriber {
func newState(state: UIState) {
updateDatabaseMenuItem.isEnabled = !state.databaseUpdating
setMainWindowStateMenuItem(state: state.mainWindowState)
setSongMenuItemsState(selectedSong: state.selectedSong)
}
}

View File

@ -0,0 +1,25 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "songIcon.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "songIcon@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 B

View 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
}
}

View File

@ -6,7 +6,7 @@
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Cocoa
import AppKit
class AlbumDetailView: NSViewController {
var album: Album?
@ -60,6 +60,8 @@ class AlbumDetailView: NSViewController {
albumTitle.stringValue = ""
albumArtist.stringValue = ""
albumCoverView.image = .defaultCoverArt
App.store.dispatch(SetSelectedSong(selectedSong: nil))
}
@IBAction func playAlbum(_ sender: NSButton) {
@ -86,6 +88,17 @@ class AlbumDetailView: NSViewController {
App.store.dispatch(MPDPlayTrack(queuePos: queueLength))
}
@IBAction func menuActionPlayNext(_ sender: Any) {
guard let song = dataSource.albumSongs[albumTracksView.clickedRow].song
else { return }
let queuePos = App.store.state.queueState.queuePos
if queuePos > -1 {
App.store.dispatch(MPDAddSongToQueue(songUri: song.mpdSong.uriString, queuePos: queuePos + 1))
}
}
@IBAction func menuActionAppendSong(_ sender: NSMenuItem) {
guard let song = dataSource.albumSongs[albumTracksView.clickedRow].song
else { return }
@ -140,83 +153,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 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
}
}

View File

@ -0,0 +1,87 @@
//
// QueueViewController+NSOutlineViewDelegate.swift
// Persephone
//
// Created by Daniel Barber on 2019/6/14.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import AppKit
extension QueueViewController: NSOutlineViewDelegate {
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 queueItem = item as? QueueItem {
switch tableColumn?.identifier.rawValue {
case "songTitleColumn":
return cellForSongTitle(outlineView, with: queueItem)
case "songArtistColumn":
return cellForSongArtist(outlineView, with: queueItem)
default:
return nil
}
} else if tableColumn?.identifier.rawValue == "songTitleColumn" {
return cellForQueueHeading(outlineView)
} else {
return nil
}
}
func outlineViewSelectionDidChange(_ notification: Notification) {
if queueView.selectedRow >= 1 {
let queueItem = dataSource.queue[queueView.selectedRow - 1]
App.store.dispatch(SetSelectedQueueItem(selectedQueueItem: queueItem))
} else {
App.store.dispatch(SetSelectedQueueItem(selectedQueueItem: nil))
}
}
func cellForSongTitle(_ outlineView: NSOutlineView, with queueItem: QueueItem) -> NSView {
let cellView = outlineView.makeView(
withIdentifier: .queueSongTitle,
owner: self
) as! QueueSongTitleView
cellView.setQueueSong(queueItem, queueIcon: dataSource.queueIcon)
return cellView
}
func cellForSongArtist(_ outlineView: NSOutlineView, with queueItem: QueueItem) -> NSView {
let cellView = outlineView.makeView(
withIdentifier: .queueSongArtist,
owner: self
) as! NSTableCellView
cellView.textField?.stringValue = queueItem.song.artist
if queueItem.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
}
}

View File

@ -9,8 +9,7 @@
import AppKit
import ReSwift
class QueueViewController: NSViewController,
NSOutlineViewDelegate {
class QueueViewController: NSViewController {
var dataSource = QueueDataSource()
@IBOutlet var queueView: NSOutlineView!
@ -25,90 +24,47 @@ class QueueViewController: NSViewController,
queueView.dataSource = dataSource
queueView.columnAutoresizingStyle = .sequentialColumnAutoresizingStyle
queueView.registerForDraggedTypes([.songPasteboardType])
queueView.draggingDestinationFeedbackStyle = .regular
}
override func keyDown(with event: NSEvent) {
switch event.keyCode {
case NSEvent.keyCodeSpace:
nextResponder?.keyDown(with: event)
case NSEvent.keyCodeBS:
let queuePos = queueView.selectedRow - 1
if queuePos >= 0 {
App.store.dispatch(MPDRemoveTrack(queuePos: queuePos))
}
default:
super.keyDown(with: event)
}
}
@IBAction func playTrack(_ sender: Any) {
let newQueuePos = queueView.selectedRow - 1
let queuePos = queueView.selectedRow - 1
if newQueuePos >= 0 {
App.store.dispatch(MPDPlayTrack(queuePos: newQueuePos))
if queuePos >= 0 {
App.store.dispatch(MPDPlayTrack(queuePos: queuePos))
}
}
@IBAction func playSongMenuAction(_ sender: NSMenuItem) {
let queuePos = queueView.clickedRow - 1
func outlineView(
_ outlineView: NSOutlineView,
selectionIndexesForProposedSelection proposedSelectionIndexes: IndexSet
) -> IndexSet {
if proposedSelectionIndexes.contains(0) {
return IndexSet()
} else {
return proposedSelectionIndexes
if queuePos >= 0 {
App.store.dispatch(MPDPlayTrack(queuePos: queuePos))
}
}
@IBAction func removeSongMenuAction(_ sender: NSMenuItem) {
let queuePos = queueView.clickedRow - 1
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
if let queueItem = item as? QueueItem {
switch tableColumn?.identifier.rawValue {
case "songTitleColumn":
return cellForSongTitle(outlineView, with: queueItem)
case "songArtistColumn":
return cellForSongArtist(outlineView, with: queueItem)
default:
return nil
}
} else if tableColumn?.identifier.rawValue == "songTitleColumn" {
return cellForQueueHeading(outlineView)
} else {
return nil
if queuePos >= 0 {
App.store.dispatch(MPDRemoveTrack(queuePos: queuePos))
}
}
func cellForSongTitle(_ outlineView: NSOutlineView, with queueItem: QueueItem) -> NSView {
let cellView = outlineView.makeView(
withIdentifier: .queueSongTitle,
owner: self
) as! QueueSongTitleView
cellView.setQueueSong(queueItem, queueIcon: dataSource.queueIcon)
return cellView
}
func cellForSongArtist(_ outlineView: NSOutlineView, with queueItem: QueueItem) -> NSView {
let cellView = outlineView.makeView(
withIdentifier: .queueSongArtist,
owner: self
) as! NSTableCellView
cellView.textField?.stringValue = queueItem.song.artist
if queueItem.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
}
}
extension QueueViewController: StoreSubscriber {

View File

@ -39,7 +39,46 @@ class AlbumTracksDataSource: NSObject, NSTableViewDataSource {
}
}
func tableView(_ tableView: NSTableView, pasteboardWriterForRow row: Int) -> NSPasteboardWriting? {
let albumSongItem = albumSongs[row]
guard let song = albumSongItem.song
else { return nil }
return NSPasteboardItem(
draggedSong: DraggedSong(
type: .albumSongItem(song.mpdSong.uriString),
title: song.title,
artist: song.artist
),
ofType: .songPasteboardType
)
}
func numberOfRows(in tableView: NSTableView) -> Int {
return albumSongs.count
}
func tableView(_ tableView: NSTableView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forRowIndexes rowIndexes: IndexSet) {
session.enumerateDraggingItems(
options: [],
for: tableView,
classes: [NSPasteboardItem.self],
searchOptions: [:]
) { draggingItem, index, stop in
guard let item = draggingItem.item as? NSPasteboardItem,
let draggedSong = item.draggedSong(forType: .songPasteboardType),
case let (title?, artist?) = (draggedSong.title, draggedSong.artist)
else { return }
draggingItem.imageComponentsProvider = {
let component = NSDraggingImageComponent(key: NSDraggingItem.ImageComponentKey.icon)
let draggedSongView = DraggedSongView(title: title, artist: artist)
component.contents = draggedSongView.view.image()
component.frame = NSRect(origin: CGPoint(), size: draggedSongView.view.image().size)
return [component]
}
}
}
}

View File

@ -35,7 +35,95 @@ class QueueDataSource: NSObject, NSOutlineViewDataSource {
if index > 0 {
return queue[index - 1]
} else {
return false
return ""
}
}
func outlineView(_ outlineView: NSOutlineView, pasteboardWriterForItem item: Any) -> NSPasteboardWriting? {
guard let queueItem = item as? QueueItem
else { return nil }
return NSPasteboardItem(
draggedSong: DraggedSong(
type: .queueItem(queueItem.queuePos),
title: queueItem.song.title,
artist: queueItem.song.artist
),
ofType: .songPasteboardType
)
}
func outlineView(_ outlineView: NSOutlineView, validateDrop info: NSDraggingInfo, proposedItem item: Any?, proposedChildIndex index: Int) -> NSDragOperation {
var newQueuePos = index - 1
guard newQueuePos >= 0,
let draggingTypes = info.draggingPasteboard.types,
draggingTypes.contains(.songPasteboardType),
let pasteboardItem = info.draggingPasteboard.pasteboardItems?.first,
let draggedSong = pasteboardItem.draggedSong(forType: .songPasteboardType)
else { return [] }
switch draggedSong.type {
case let .queueItem(queuePos):
if newQueuePos > queuePos { newQueuePos -= 1 }
guard queuePos != newQueuePos
else { return [] }
return .move
case .albumSongItem:
return .copy
}
}
func outlineView(_ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool {
var newQueuePos = index - 1
guard let draggingTypes = info.draggingPasteboard.types,
draggingTypes.contains(.songPasteboardType),
let data = info.draggingPasteboard.data(forType: .songPasteboardType),
let draggedSong = try? PropertyListDecoder().decode(DraggedSong.self, from: data)
else { return false }
switch draggedSong.type {
case let .queueItem(queuePos):
if newQueuePos > queuePos { newQueuePos -= 1 }
guard queuePos != newQueuePos
else { return false }
App.store.dispatch(MPDMoveSongInQueue(oldQueuePos: queuePos, newQueuePos: newQueuePos))
return true
case let .albumSongItem(uri):
App.store.dispatch(MPDAddSongToQueue(songUri: uri, queuePos: newQueuePos))
return true
}
}
func outlineView(_ outlineView: NSOutlineView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItems draggedItems: [Any]) {
session.enumerateDraggingItems(
options: [],
for: outlineView,
classes: [NSPasteboardItem.self],
searchOptions: [:]
) { draggingItem, index, stop in
guard let item = draggingItem.item as? NSPasteboardItem,
let data = item.data(forType: .songPasteboardType),
let draggedSong = try? PropertyListDecoder().decode(DraggedSong.self, from: data),
case let (title?, artist?) = (draggedSong.title, draggedSong.artist)
else { return }
draggingItem.imageComponentsProvider = {
let component = NSDraggingImageComponent(key: NSDraggingItem.ImageComponentKey.icon)
let draggedSongView = DraggedSongView(title: title, artist: artist)
let view = draggedSongView.view
let image = view.image()
component.contents = image
component.frame = NSRect(origin: CGPoint(), size: view.frame.size)
return [component]
}
}
}
}

View File

@ -10,4 +10,5 @@ import AppKit
extension NSEvent {
static let keyCodeSpace: UInt16 = 49
static let keyCodeBS: UInt16 = 51
}

View File

@ -0,0 +1,13 @@
//
// NSPasteboard.swift
// Persephone
//
// Created by Daniel Barber on 2019/6/17.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import AppKit
extension NSPasteboard.PasteboardType {
static let songPasteboardType = NSPasteboard.PasteboardType("me.danbarber.persephone")
}

View File

@ -0,0 +1,29 @@
//
// NSPasteboardItem.swift
// Persephone
//
// Created by Daniel Barber on 2019/6/28.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import AppKit
extension NSPasteboardItem {
convenience init(draggedSong: DraggedSong, ofType type: NSPasteboard.PasteboardType) {
self.init()
self.setDraggedSong(draggedSong, forType: type)
}
func setDraggedSong(_ draggedSong: DraggedSong, forType type: NSPasteboard.PasteboardType) {
let data = try! PropertyListEncoder().encode(draggedSong)
setData(data, forType: type)
}
func draggedSong(forType type: NSPasteboard.PasteboardType) -> DraggedSong? {
guard let itemData = data(forType: type)
else { return nil }
return try? PropertyListDecoder().decode(DraggedSong.self, from: itemData)
}
}

View File

@ -0,0 +1,18 @@
//
// NSView.swift
// Persephone
//
// Created by Daniel Barber on 2019/6/18.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import AppKit
extension NSView {
func image() -> NSImage {
layoutSubtreeIfNeeded()
let imageRepresentation = bitmapImageRepForCachingDisplay(in: frame)!
cacheDisplay(in: frame, to: imageRepresentation)
return NSImage(cgImage: imageRepresentation.cgImage!, size: frame.size)
}
}

View File

@ -54,6 +54,8 @@ extension MPDClient {
guard let queuePos = userData["queuePos"] as? Int
else { return }
sendPlayTrack(at: queuePos)
case .clearQueue:
sendClearQueue()
case .replaceQueue:
guard let songs = userData["songs"] as? [MPDSong]
else { return }
@ -62,6 +64,22 @@ extension MPDClient {
guard let song = userData["song"] as? MPDSong
else { return }
sendAppendSong(song)
case .removeSong:
guard let queuePos = userData["queuePos"] as? Int
else { return }
sendRemoveSong(at: queuePos)
case .moveSongInQueue:
guard let oldQueuePos = userData["oldQueuePos"] as? Int,
let newQueuePos = userData["newQueuePos"] as? Int
else { return }
sendMoveSongInQueue(at: oldQueuePos, to: newQueuePos)
case .addSongToQueue:
guard let songUri = userData["uri"] as? String,
let queuePos = userData["queuePos"] as? Int
else { return }
sendAddSongToQueue(uri: songUri, at: queuePos)
// Album commands
case .fetchAllAlbums:

View File

@ -37,7 +37,12 @@ extension MPDClient {
}
if mpdIdle.contains(.queue) {
self.fetchQueue()
self.fetchStatus()
self.delegate?.didUpdateQueue(mpdClient: self, queue: self.queue)
if let status = self.status {
self.delegate?.didUpdateQueuePos(mpdClient: self, song: status.song)
}
}
if mpdIdle.contains(.player) || mpdIdle.contains(.options) {
self.fetchStatus()

View File

@ -14,6 +14,10 @@ extension MPDClient {
sendCommand(command: .fetchQueue)
}
func clearQueue() {
enqueueCommand(command: .clearQueue)
}
func playTrack(at queuePos: Int) {
enqueueCommand(command: .playTrack, userData: ["queuePos": queuePos])
}
@ -22,6 +26,18 @@ extension MPDClient {
enqueueCommand(command: .appendSong, userData: ["song": song])
}
func removeSong(at queuePos: Int) {
enqueueCommand(command: .removeSong, userData: ["queuePos": queuePos])
}
func moveSongInQueue(at queuePos: Int, to newQueuePos: Int) {
enqueueCommand(command: .moveSongInQueue, userData: ["oldQueuePos": queuePos, "newQueuePos": newQueuePos])
}
func addSongToQueue(songUri: String, at queuePos: Int) {
enqueueCommand(command: .addSongToQueue, userData: ["uri": songUri, "queuePos": queuePos])
}
func sendPlayTrack(at queuePos: Int) {
mpd_run_play_pos(self.connection, UInt32(queuePos))
}
@ -36,6 +52,10 @@ extension MPDClient {
}
}
func sendClearQueue() {
mpd_run_clear(self.connection)
}
func sendReplaceQueue(_ songs: [MPDSong]) {
mpd_run_clear(self.connection)
@ -48,4 +68,16 @@ extension MPDClient {
func sendAppendSong(_ song: MPDSong) {
mpd_run_add(self.connection, song.uri)
}
func sendRemoveSong(at queuePos: Int) {
mpd_run_delete(self.connection, UInt32(queuePos))
}
func sendMoveSongInQueue(at oldQueuePos: Int, to newQueuePos: Int) {
mpd_run_move(self.connection, UInt32(oldQueuePos), UInt32(newQueuePos))
}
func sendAddSongToQueue(uri: String, at queuePos: Int) {
mpd_run_add_id_to(self.connection, uri, UInt32(queuePos))
}
}

View File

@ -29,8 +29,12 @@ extension MPDClient {
// Queue commands
case fetchQueue
case playTrack
case clearQueue
case replaceQueue
case appendSong
case removeSong
case moveSongInQueue
case addSongToQueue
// Album commands
case fetchAllAlbums

View File

@ -0,0 +1,13 @@
//
// DraggedSong.swift
// Persephone
//
// Created by Daniel Barber on 2019/6/21.
// Copyright © 2019 Dan Barber. All rights reserved.
//
struct DraggedSong: Codable {
var type: DraggedSongType
var title: String?
var artist: String?
}

View File

@ -0,0 +1,50 @@
//
// DraggedSongType.swift
// Persephone
//
// Created by Daniel Barber on 2019/6/21.
// Copyright © 2019 Dan Barber. All rights reserved.
//
enum DraggedSongType {
case queueItem(Int)
case albumSongItem(String)
}
private enum Discriminator: Int, Codable {
case queueItem
case albumSongItem
}
extension DraggedSongType: Codable {
enum CodingKeys: String, CodingKey {
case type
case value
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case let .queueItem(queuePos):
try container.encode(Discriminator.queueItem, forKey: .type)
try container.encode(queuePos, forKey: .value)
case let .albumSongItem(uri):
try container.encode(Discriminator.albumSongItem, forKey: .type)
try container.encode(uri, forKey: .value)
}
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(Discriminator.self, forKey: .type)
switch type {
case .queueItem:
let queuePos = try container.decode(Int.self, forKey: .value)
self = .queueItem(queuePos)
case .albumSongItem:
let uri = try container.decode(String.self, forKey: .value)
self = .albumSongItem(uri)
}
}
}

View File

@ -6,9 +6,9 @@
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Foundation
import AppKit
struct QueueItem: Equatable {
struct QueueItem: Hashable {
var song: Song
var queuePos: Int
var isPlaying: Bool

View File

@ -43,3 +43,14 @@ extension Song: Equatable {
(lhs.album == rhs.album)
}
}
extension Song: Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(mpdSong.uriString)
hasher.combine(disc)
hasher.combine(trackNumber)
hasher.combine(title)
hasher.combine(artist)
hasher.combine(album.title)
}
}

View File

@ -250,12 +250,19 @@
</customView>
<menu id="qbK-4f-3fG">
<items>
<menuItem title="Play Song" id="poo-OI-Kwi">
<menuItem title="Play Now" id="poo-OI-Kwi">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="menuActionPlaySong:" target="-2" id="ZB9-dq-reF"/>
</connections>
</menuItem>
<menuItem title="Play Next" id="78G-Sy-J8P">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="menuActionPlayNext:" target="-2" id="hMo-gT-IcI"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="Hk6-In-qd2"/>
<menuItem title="Add Song to Queue" id="PdP-4s-xfR">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>

View File

@ -75,6 +75,51 @@
</items>
</menu>
</menuItem>
<menuItem title="Queue" id="zZL-2K-acp">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Queue" id="CWM-rf-Ozu">
<items>
<menuItem title="Remove Song" id="x9q-fx-zaQ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="removeQueueSongMenuAction:" target="Voe-Tx-rLC" id="stm-VC-mfp"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="MG8-dW-RM9"/>
<menuItem title="Clear…" id="x6w-87-3xV">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="clearQueueMenuAction:" target="Voe-Tx-rLC" id="q00-ts-Swv"/>
</connections>
</menuItem>
</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 Now" 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="Play Next" id="Q8j-jr-IOp">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="playSelectedSongNextAction:" target="Voe-Tx-rLC" id="HQR-6p-8g7"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="ml8-jV-bZq"/>
<menuItem title="Add 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 +177,10 @@
</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="playSelectedSongNextMenuItem" destination="Q8j-jr-IOp" id="Jqh-ia-sMK"/>
<outlet property="updateDatabaseMenuItem" destination="EJg-93-1F6" id="gMf-SQ-lyI"/>
</connections>
</customObject>
@ -534,21 +582,21 @@
<scene sceneID="QcX-dC-cTZ">
<objects>
<viewController id="KIP-rq-4dM" customClass="QueueViewController" customModule="Persephone" customModuleProvider="target" sceneMemberID="viewController">
<splitView key="view" wantsLayer="YES" dividerStyle="thin" id="84I-w3-Mxl">
<splitView key="view" dividerStyle="thin" id="84I-w3-Mxl">
<rect key="frame" x="0.0" y="0.0" width="328" height="548"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" usesPredominantAxisScrolling="NO" id="S3o-nF-NN7">
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="17" horizontalPageScroll="10" verticalLineScroll="17" verticalPageScroll="10" usesPredominantAxisScrolling="NO" id="S3o-nF-NN7">
<rect key="frame" x="0.0" y="0.0" width="328" height="219"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<clipView key="contentView" drawsBackground="NO" id="WI8-Pw-03L">
<rect key="frame" x="0.0" y="0.0" width="328" height="219"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" selectionHighlightStyle="sourceList" multipleSelection="NO" autosaveColumns="NO" rowSizeStyle="automatic" viewBased="YES" indentationPerLevel="16" outlineTableColumn="0Co-uF-CCB" id="jEJ-jg-fll">
<outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" selectionHighlightStyle="sourceList" multipleSelection="NO" autosaveColumns="NO" rowSizeStyle="automatic" viewBased="YES" indentationPerLevel="14" outlineTableColumn="0Co-uF-CCB" id="jEJ-jg-fll" customClass="QueueView" customModule="Persephone" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="328" height="219"/>
<autoresizingMask key="autoresizingMask"/>
<size key="intercellSpacing" width="3" height="2"/>
<size key="intercellSpacing" width="3" height="0.0"/>
<color key="backgroundColor" name="_sourceListBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns>
@ -566,7 +614,7 @@
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES"/>
<prototypeCellViews>
<tableCellView identifier="queueHeadingCell" id="GOd-cg-juD">
<rect key="frame" x="1" y="1" width="200" height="17"/>
<rect key="frame" x="1" y="0.0" width="200" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField verticalHuggingPriority="750" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="xgd-Cz-np3">
@ -587,7 +635,7 @@
</constraints>
</tableCellView>
<tableCellView identifier="songTitleCell" id="5rR-Gz-AcP" customClass="QueueSongTitleView" customModule="Persephone" customModuleProvider="target">
<rect key="frame" x="1" y="20" width="200" height="17"/>
<rect key="frame" x="1" y="17" width="200" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView identifier="queuePlayerState" translatesAutoresizingMaskIntoConstraints="NO" id="o8i-cz-hIP" userLabel="Player State View">
@ -647,7 +695,7 @@
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES"/>
<prototypeCellViews>
<tableCellView identifier="songArtistCell" id="JSk-Vc-Y7e">
<rect key="frame" x="204" y="1" width="122" height="17"/>
<rect key="frame" x="204" y="0.0" width="122" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="tBe-Q9-3Rw">
@ -674,6 +722,7 @@
<connections>
<action trigger="doubleAction" selector="playTrack:" target="KIP-rq-4dM" id="opa-6G-OW0"/>
<outlet property="delegate" destination="KIP-rq-4dM" id="60F-6x-bUE"/>
<outlet property="menu" destination="dYA-Jm-eOa" id="9s2-7K-tVx"/>
</connections>
</outlineView>
</subviews>
@ -724,8 +773,24 @@
</connections>
</viewController>
<customObject id="du4-e9-TfX" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
<menu identifier="queueViewMenu" id="dYA-Jm-eOa">
<items>
<menuItem title="Play" id="kp1-XJ-9CL">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="playSongMenuAction:" target="KIP-rq-4dM" id="1nO-8z-LT2"/>
</connections>
</menuItem>
<menuItem title="Remove" id="GaJ-qk-Cg4">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="removeSongMenuAction:" target="KIP-rq-4dM" id="0Oc-Z3-4OD"/>
</connections>
</menuItem>
</items>
</menu>
</objects>
<point key="canvasLocation" x="820" y="749"/>
<point key="canvasLocation" x="796" y="848"/>
</scene>
<!--Album View Controller-->
<scene sceneID="7Ua-Hj-zWt">
@ -748,6 +813,7 @@
<color key="primaryBackgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<connections>
<outlet property="delegate" destination="gPn-fP-LFc" id="LQ2-Vl-r08"/>
<outlet property="menu" destination="Rif-KP-4xb" id="f7w-Ot-TKf"/>
</connections>
</collectionView>
</subviews>
@ -775,8 +841,18 @@
</connections>
</viewController>
<customObject id="uex-Ws-5X4" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
<menu id="Rif-KP-4xb">
<items>
<menuItem title="Play album" id="Cuu-eF-cPb">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem title="Add album to queue" id="pUA-0C-zhs">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
</items>
</menu>
</objects>
<point key="canvasLocation" x="531" y="1358"/>
<point key="canvasLocation" x="329" y="1370"/>
</scene>
</scenes>
<resources>

View File

@ -0,0 +1,92 @@
<?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="DraggedSongView" customModule="Persephone" customModuleProvider="target">
<connections>
<outlet property="artistLabel" destination="egC-YO-gnV" id="g0f-FI-cGF"/>
<outlet property="titleLabel" destination="tsD-ub-h7I" id="EVO-Z7-tZY"/>
<outlet property="view" destination="Hz6-mo-xeY" id="Wf9-hm-E7M"/>
</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="129" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<box boxType="custom" borderType="line" cornerRadius="4" translatesAutoresizingMaskIntoConstraints="NO" id="G2x-p5-RM6">
<rect key="frame" x="0.0" y="0.0" width="129" height="22"/>
<view key="contentView" id="qbG-wn-N4h">
<rect key="frame" x="1" y="1" width="127" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView distribution="fillProportionally" orientation="horizontal" alignment="centerY" spacing="12" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="rlT-HP-okZ">
<rect key="frame" x="0.0" y="0.0" width="127" height="20"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="Go5-Lk-qgA">
<rect key="frame" x="8" y="2" width="17" height="17"/>
<constraints>
<constraint firstAttribute="height" constant="17" id="DKw-m3-Lne"/>
<constraint firstAttribute="width" constant="17" id="k19-kI-PmI"/>
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="songIcon" id="KDX-a9-N4B"/>
</imageView>
<textField horizontalHuggingPriority="1000" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="tsD-ub-h7I">
<rect key="frame" x="35" y="2" width="32" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" title="Title" usesSingleLineMode="YES" id="rMn-D0-PG7">
<font key="font" metaFont="systemMedium" size="13"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="1000" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="egC-YO-gnV">
<rect key="frame" x="75" y="2" width="38" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" alignment="left" title="Artist" usesSingleLineMode="YES" id="m15-pn-bdW">
<font key="font" metaFont="system"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<edgeInsets key="edgeInsets" left="8" right="16" top="0.0" bottom="0.0"/>
<visibilityPriorities>
<real value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="rlT-HP-okZ" secondAttribute="trailing" id="3dK-4q-G3z"/>
<constraint firstAttribute="bottom" secondItem="rlT-HP-okZ" secondAttribute="bottom" id="6bV-EI-Qk5"/>
<constraint firstItem="rlT-HP-okZ" firstAttribute="leading" secondItem="qbG-wn-N4h" secondAttribute="leading" id="d0B-7v-cmT"/>
<constraint firstItem="rlT-HP-okZ" firstAttribute="top" secondItem="qbG-wn-N4h" secondAttribute="top" id="mvZ-UP-tHf"/>
</constraints>
</view>
<color key="borderColor" name="tertiaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="fillColor" red="0.27450980392156865" green="0.27450980392156865" blue="0.27450980392156865" alpha="0.65297645246478875" colorSpace="custom" customColorSpace="sRGB"/>
</box>
</subviews>
<constraints>
<constraint firstItem="G2x-p5-RM6" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" id="A72-wi-zNE"/>
<constraint firstItem="G2x-p5-RM6" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" id="ENp-3t-AXT"/>
<constraint firstAttribute="trailing" secondItem="G2x-p5-RM6" secondAttribute="trailing" id="MbJ-MP-4HG"/>
<constraint firstAttribute="bottom" secondItem="G2x-p5-RM6" secondAttribute="bottom" id="Nzd-OC-f1V"/>
</constraints>
<point key="canvasLocation" x="53.5" y="14"/>
</customView>
</objects>
<resources>
<image name="songIcon" width="17" height="17"/>
</resources>
</document>

View File

@ -16,10 +16,26 @@ struct MPDStopAction: Action {}
struct MPDNextTrackAction: Action {}
struct MPDPrevTrackAction: Action {}
struct MPDClearQueue: Action {}
struct MPDMoveSongInQueue: Action {
let oldQueuePos: Int
let newQueuePos: Int
}
struct MPDAddSongToQueue: Action {
let songUri: String
let queuePos: Int
}
struct MPDAppendTrack: Action {
let song: MPDClient.MPDSong
}
struct MPDRemoveTrack: Action {
let queuePos: Int
}
struct MPDPlayTrack: Action {
let queuePos: Int
}

View File

@ -17,3 +17,11 @@ struct MainWindowDidMinimizeAction: Action {}
struct DatabaseUpdateStartedAction: Action {}
struct DatabaseUpdateFinishedAction: Action {}
struct SetSelectedQueueItem: Action {
let selectedQueueItem: QueueItem?
}
struct SetSelectedSong: Action {
let selectedSong: Song?
}

View File

@ -30,9 +30,21 @@ func mpdReducer(action: Action, state: MPDState?) -> MPDState {
case is MPDPrevTrackAction:
App.mpdClient.prevTrack()
case is MPDClearQueue:
App.mpdClient.clearQueue()
case let action as MPDMoveSongInQueue:
App.mpdClient.moveSongInQueue(at: action.oldQueuePos, to: action.newQueuePos)
case let action as MPDAddSongToQueue:
App.mpdClient.addSongToQueue(songUri: action.songUri, at: action.queuePos)
case let action as MPDAppendTrack:
App.mpdClient.appendSong(action.song)
case let action as MPDRemoveTrack:
App.mpdClient.removeSong(at: action.queuePos)
case let action as MPDPlayTrack:
App.mpdClient.playTrack(at: action.queuePos)

View File

@ -14,7 +14,9 @@ func queueReducer(action: Action, state: QueueState?) -> QueueState {
switch action {
case let action as UpdateQueueAction:
state.queuePos = -1
if state.queuePos >= action.queue.count {
state.queuePos = -1
}
state.queue = action.queue.enumerated().map { index, mpdSong in
let song = Song(mpdSong: mpdSong)

View File

@ -27,6 +27,12 @@ func uiReducer(action: Action, state: UIState?) -> UIState {
case is DatabaseUpdateFinishedAction:
state.databaseUpdating = false
case let action as SetSelectedSong:
state.selectedSong = action.selectedSong
case let action as SetSelectedQueueItem:
state.selectedQueueItem = action.selectedQueueItem
default:
break
}

View File

@ -18,4 +18,8 @@ struct UIState: StateType {
var mainWindowState: MainWindowState = .closed
var databaseUpdating: Bool = false
var selectedSong: Song?
var selectedQueueItem: QueueItem?
}

View File

@ -0,0 +1,33 @@
//
// DraggedSong.swift
// Persephone
//
// Created by Daniel Barber on 2019/6/18.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Cocoa
class DraggedSongView: NSViewController {
@IBOutlet var titleLabel: NSTextField!
@IBOutlet var artistLabel: NSTextField!
private let songTitle: String
private let songArtist: String
init(title: String, artist: String) {
songTitle = title
songArtist = artist
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
titleLabel.stringValue = songTitle
artistLabel.stringValue = songArtist
}
}

View File

@ -0,0 +1,23 @@
//
// QueueView.swift
// Persephone
//
// Created by Daniel Barber on 2019/6/09.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import AppKit
class QueueView: NSOutlineView {
override func menu(for event: NSEvent) -> NSMenu? {
let point = convert(event.locationInWindow, from: nil)
let currentRow = row(at: point)
if currentRow > 0 {
return super.menu(for: event)
} else {
return nil
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 B

Binary file not shown.