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

Compare commits

..

10 Commits

Author SHA1 Message Date
b1fd9db2fb
WIP: Add Alamofire 2019-02-26 21:57:42 -05:00
8b7a43fb07
WIP: Create operation queue for album art download 2019-02-26 21:46:39 -05:00
579d695dca
WIP: Fetch album art from MusicBrainz 2019-02-26 09:36:18 -05:00
e21680a890
Better temp icon 2019-02-23 12:47:21 -05:00
38fc309606
Update version to something sensible 2019-02-22 22:16:06 -05:00
1fda804258
Add MediaKeyTap to capture media keys 2019-02-22 21:13:56 -05:00
52b0a3b067
Add track progress bar
Adds a slider control that shows the track playback progress. The slider
can be used to seek through the track.
2019-02-22 20:59:01 -05:00
52e2e7c62c
Add new screenshot 2019-02-22 20:36:16 -05:00
2d6aa478a7
Add a button to albums that plays the album
This works by clearing the queue, adding all the tracks from the album
to the queue and playing the first track.

Fix bug where resize might leave a play button visible

Had to refactor the QueueView somewhat as there was a bug that only
surfaced on clearing and refilling the playlist. The bug was due to the
way NSOutlineView reuses subviews.

Add screenshot with album play button

Move queue datasource and refactor view controller

Move album datasource out of the view controller
2019-02-22 13:09:09 -05:00
5d0e1c4719
Spacebar triggers play/pause
Use the responder chain to handle the keypress.
2019-02-22 11:58:56 -05:00
59 changed files with 1222 additions and 848 deletions

1
.gitignore vendored
View File

@ -61,6 +61,7 @@ playground.xcworkspace
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage
Carthage/Build
# fastlane

View File

@ -1,2 +1,3 @@
brew "carthage"
brew "meson"
brew "ninja"

4
Cartfile Normal file
View File

@ -0,0 +1,4 @@
github "Alamofire/Alamofire"
github "SwiftyJSON/SwiftyJSON" ~> 4.0
github "mxcl/PromiseKit" ~> 6.8
github "nhurden/MediaKeyTap" "fix-tis-tsm-error"

4
Cartfile.resolved Normal file
View File

@ -0,0 +1,4 @@
github "Alamofire/Alamofire" "4.8.1"
github "SwiftyJSON/SwiftyJSON" "4.2.0"
github "mxcl/PromiseKit" "6.8.3"
github "nhurden/MediaKeyTap" "355d346c56243e6d56487fa46fcad945251e16ae"

View File

@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objectVersion = 51;
objects = {
/* Begin PBXBuildFile section */
@ -16,25 +16,53 @@
E408D3B9220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3B8220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift */; };
E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3BD220E03EE0006D9BE /* RawRepresentable.swift */; };
E408D3C2220E134F0006D9BE /* AlbumViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3C1220E134F0006D9BE /* AlbumViewController.swift */; };
E408D3CA220E341D0006D9BE /* AlbumItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3C8220E341D0006D9BE /* AlbumItem.swift */; };
E408D3CB220E341D0006D9BE /* AlbumItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = E408D3C9220E341D0006D9BE /* AlbumItem.xib */; };
E40FE719221B48E300A4223F /* AlbumViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = E40FE718221B48E300A4223F /* AlbumViewLayout.swift */; };
E408D3CB220E341D0006D9BE /* AlbumViewItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = E408D3C9220E341D0006D9BE /* AlbumViewItem.xib */; };
E40F41F3221EDE27004B6CB8 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = E40F41F2221EDE27004B6CB8 /* Preferences.swift */; };
E40FE71B221B904300A4223F /* NSEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = E40FE71A221B904300A4223F /* NSEvent.swift */; };
E41B22C021FB6BBA00D544F6 /* libmpdclient.2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */; settings = {ATTRIBUTES = (Required, ); }; };
E41B22C121FB6C3300D544F6 /* libmpdclient.2.dylib in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
E41B22C621FB932700D544F6 /* MPDClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41B22C521FB932700D544F6 /* MPDClient.swift */; };
E41EA46C221636AF0068EF46 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EA46B221636AF0068EF46 /* PreferencesViewController.swift */; };
E41EA46F221715910068EF46 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EA46E221715910068EF46 /* Preferences.swift */; };
E41EA46C221636AF0068EF46 /* GeneralPrefsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */; };
E421ACA3221F73C4008B2449 /* MediaKeyTap.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E421ACA1221F73B8008B2449 /* MediaKeyTap.framework */; };
E421ACA4221F73C4008B2449 /* MediaKeyTap.framework in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E421ACA1221F73B8008B2449 /* MediaKeyTap.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E42A8F3B22176D6400A13ED9 /* LICENSE.md in Resources */ = {isa = PBXBuildFile; fileRef = E42A8F3922176D6400A13ED9 /* LICENSE.md */; };
E42A8F3C22176D6400A13ED9 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = E42A8F3A22176D6400A13ED9 /* README.md */; };
E435E3E2221CD4E200184CFC /* NSFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435E3E1221CD4E200184CFC /* NSFont.swift */; };
E435E3E4221CD75D00184CFC /* NSImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435E3E3221CD75D00184CFC /* NSImage.swift */; };
E450AD7E222620A10091BED3 /* AlbumItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E450AD7D222620A10091BED3 /* AlbumItem.swift */; };
E450AD8622262AE60091BED3 /* SwiftyJSON.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E450AD8522262AE60091BED3 /* SwiftyJSON.framework */; };
E450AD8822262AEC0091BED3 /* SwiftyJSON.framework in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E450AD8522262AE60091BED3 /* SwiftyJSON.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E450AD8B22262B590091BED3 /* AlbumArtOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E450AD8A22262B590091BED3 /* AlbumArtOperation.swift */; };
E450AD8F22262C620091BED3 /* PromiseKit.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E450AD8E22262C620091BED3 /* PromiseKit.framework.dSYM */; };
E450AD9122262C780091BED3 /* SwiftyJSON.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E450AD9022262C780091BED3 /* SwiftyJSON.framework.dSYM */; };
E450AD9222262C970091BED3 /* PromiseKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E450AD8C22262C590091BED3 /* PromiseKit.framework */; };
E450AD9322262C970091BED3 /* PromiseKit.framework in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E450AD8C22262C590091BED3 /* PromiseKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E450AD9522262DF10091BED3 /* AlbumArtOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = E450AD9422262DF10091BED3 /* AlbumArtOperations.swift */; };
E450AD98222633920091BED3 /* Alamofire.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E450AD96222633920091BED3 /* Alamofire.framework.dSYM */; };
E450AD9A222633AB0091BED3 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E450AD97222633920091BED3 /* Alamofire.framework */; };
E450AD9B222633AB0091BED3 /* Alamofire.framework in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E450AD97222633920091BED3 /* Alamofire.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E465049A21E94DF500A70F4C /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E465049921E94DF500A70F4C /* WindowController.swift */; };
E47E2FCC2220573500F747E6 /* MediaKeyTap.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E47E2FCB2220573500F747E6 /* MediaKeyTap.framework.dSYM */; };
E47E2FD122205C4600F747E6 /* MainSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FD022205C4600F747E6 /* MainSplitViewController.swift */; };
E47E2FD322205D2500F747E6 /* MainWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FD222205D2500F747E6 /* MainWindow.swift */; };
E47E2FD5222071FD00F747E6 /* AlbumViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FD4222071FD00F747E6 /* AlbumViewItem.swift */; };
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 */; };
E4928E0B2218D62A001D4BEA /* CGColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4928E0A2218D62A001D4BEA /* CGColor.swift */; };
E4A642DA22090CBE00067D21 /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A642D922090CBE00067D21 /* Status.swift */; };
E4A83BEF2221F8CF0098FED6 /* AlbumArtPrefsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BEE2221F8CF0098FED6 /* AlbumArtPrefsController.swift */; };
E4A83BF12221FAA00098FED6 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */; };
E4A83BF4222207D50098FED6 /* AlbumArtService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BF3222207D50098FED6 /* AlbumArtService.swift */; };
E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC8F2204EC7F0024217A /* Delegate.swift */; };
E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC912204F4B80024217A /* QueueViewController.swift */; };
E4E8CC942206097F0024217A /* NotificationsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC932206097F0024217A /* NotificationsController.swift */; };
E4E8CC9A22075D370024217A /* Song.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC9922075D370024217A /* Song.swift */; };
E4EB2379220F10B8008C70C0 /* Pair.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4EB2378220F10B8008C70C0 /* Pair.swift */; };
E4EB237B220F7CF1008C70C0 /* Album.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4EB237A220F7CF1008C70C0 /* Album.swift */; };
E4F6B460221E119B00ACF42A /* QueueDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B45F221E119B00ACF42A /* QueueDataSource.swift */; };
E4F6B463221E125900ACF42A /* SongItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B462221E125900ACF42A /* SongItem.swift */; };
E4F6B467221E233200ACF42A /* AlbumDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B466221E233200ACF42A /* AlbumDataSource.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -62,10 +90,23 @@
dstSubfolderSpec = 10;
files = (
E41B22C121FB6C3300D544F6 /* libmpdclient.2.dylib in Embed Libraries */,
E421ACA4221F73C4008B2449 /* MediaKeyTap.framework in Embed Libraries */,
E450AD8822262AEC0091BED3 /* SwiftyJSON.framework in Embed Libraries */,
E450AD9B222633AB0091BED3 /* Alamofire.framework in Embed Libraries */,
E450AD9322262C970091BED3 /* PromiseKit.framework in Embed Libraries */,
);
name = "Embed Libraries";
runOnlyForDeploymentPostprocessing = 0;
};
E421AC9B221F7319008B2449 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 16;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
@ -85,9 +126,9 @@
E408D3B8220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSUserInterfaceItemIdentifier.swift; sourceTree = "<group>"; };
E408D3BD220E03EE0006D9BE /* RawRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawRepresentable.swift; sourceTree = "<group>"; };
E408D3C1220E134F0006D9BE /* AlbumViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumViewController.swift; sourceTree = "<group>"; };
E408D3C8220E341D0006D9BE /* AlbumItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumItem.swift; sourceTree = "<group>"; };
E408D3C9220E341D0006D9BE /* AlbumItem.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AlbumItem.xib; sourceTree = "<group>"; };
E40FE718221B48E300A4223F /* AlbumViewLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumViewLayout.swift; sourceTree = "<group>"; };
E408D3C9220E341D0006D9BE /* AlbumViewItem.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AlbumViewItem.xib; sourceTree = "<group>"; };
E40F41F2221EDE27004B6CB8 /* Preferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
E40FE71A221B904300A4223F /* NSEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSEvent.swift; sourceTree = "<group>"; };
E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libmpdclient.2.dylib; path = libmpdclient/output/libmpdclient.2.dylib; sourceTree = "<group>"; };
E41B22C421FB715A00D544F6 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = "<group>"; };
E41B22C521FB932700D544F6 /* MPDClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDClient.swift; sourceTree = "<group>"; };
@ -126,19 +167,43 @@
E41B22E921FB966C00D544F6 /* capabilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = capabilities.h; sourceTree = "<group>"; };
E41B22EA21FB966C00D544F6 /* queue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = queue.h; sourceTree = "<group>"; };
E41B22EB21FB966C00D544F6 /* playlist.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = playlist.h; sourceTree = "<group>"; };
E41EA46B221636AF0068EF46 /* PreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesViewController.swift; sourceTree = "<group>"; };
E41EA46E221715910068EF46 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPrefsViewController.swift; sourceTree = "<group>"; };
E421ACA1221F73B8008B2449 /* MediaKeyTap.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaKeyTap.framework; path = Carthage/Build/Mac/MediaKeyTap.framework; sourceTree = "<group>"; };
E42A8F3922176D6400A13ED9 /* LICENSE.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = "<group>"; };
E42A8F3A22176D6400A13ED9 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
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>"; };
E450AD7D222620A10091BED3 /* AlbumItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumItem.swift; sourceTree = "<group>"; };
E450AD8522262AE60091BED3 /* SwiftyJSON.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftyJSON.framework; path = Carthage/Build/Mac/SwiftyJSON.framework; sourceTree = "<group>"; };
E450AD8A22262B590091BED3 /* AlbumArtOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumArtOperation.swift; sourceTree = "<group>"; };
E450AD8C22262C590091BED3 /* PromiseKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PromiseKit.framework; path = Carthage/Build/Mac/PromiseKit.framework; sourceTree = "<group>"; };
E450AD8E22262C620091BED3 /* PromiseKit.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = PromiseKit.framework.dSYM; path = Carthage/Build/Mac/PromiseKit.framework.dSYM; sourceTree = "<group>"; };
E450AD9022262C780091BED3 /* SwiftyJSON.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = SwiftyJSON.framework.dSYM; path = Carthage/Build/Mac/SwiftyJSON.framework.dSYM; sourceTree = "<group>"; };
E450AD9422262DF10091BED3 /* AlbumArtOperations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumArtOperations.swift; sourceTree = "<group>"; };
E450AD96222633920091BED3 /* Alamofire.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = Alamofire.framework.dSYM; path = Carthage/Build/Mac/Alamofire.framework.dSYM; sourceTree = "<group>"; };
E450AD97222633920091BED3 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = Carthage/Build/Mac/Alamofire.framework; sourceTree = "<group>"; };
E465049921E94DF500A70F4C /* WindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowController.swift; sourceTree = "<group>"; };
E47E2FCB2220573500F747E6 /* MediaKeyTap.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = MediaKeyTap.framework.dSYM; path = Carthage/Build/Mac/MediaKeyTap.framework.dSYM; sourceTree = "<group>"; };
E47E2FD022205C4600F747E6 /* MainSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSplitViewController.swift; sourceTree = "<group>"; };
E47E2FD222205D2500F747E6 /* MainWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainWindow.swift; sourceTree = "<group>"; };
E47E2FD4222071FD00F747E6 /* AlbumViewItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumViewItem.swift; sourceTree = "<group>"; };
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>"; };
E4928E0A2218D62A001D4BEA /* CGColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGColor.swift; sourceTree = "<group>"; };
E4A642D922090CBE00067D21 /* Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Status.swift; sourceTree = "<group>"; };
E4A83BEE2221F8CF0098FED6 /* AlbumArtPrefsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumArtPrefsController.swift; sourceTree = "<group>"; };
E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesViewController.swift; sourceTree = "<group>"; };
E4A83BF3222207D50098FED6 /* AlbumArtService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumArtService.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>"; };
E4E8CC932206097F0024217A /* NotificationsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsController.swift; sourceTree = "<group>"; };
E4E8CC9922075D370024217A /* Song.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Song.swift; sourceTree = "<group>"; };
E4EB2378220F10B8008C70C0 /* Pair.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pair.swift; sourceTree = "<group>"; };
E4EB237A220F7CF1008C70C0 /* Album.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Album.swift; sourceTree = "<group>"; };
E4F6B45F221E119B00ACF42A /* QueueDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueDataSource.swift; sourceTree = "<group>"; };
E4F6B462221E125900ACF42A /* SongItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SongItem.swift; sourceTree = "<group>"; };
E4F6B466221E233200ACF42A /* AlbumDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumDataSource.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -147,6 +212,10 @@
buildActionMask = 2147483647;
files = (
E41B22C021FB6BBA00D544F6 /* libmpdclient.2.dylib in Frameworks */,
E421ACA3221F73C4008B2449 /* MediaKeyTap.framework in Frameworks */,
E450AD8622262AE60091BED3 /* SwiftyJSON.framework in Frameworks */,
E450AD9A222633AB0091BED3 /* Alamofire.framework in Frameworks */,
E450AD9222262C970091BED3 /* PromiseKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -193,8 +262,12 @@
E407861A2110CE6E006887B1 /* Persephone */ = {
isa = PBXGroup;
children = (
E40FE717221B48CE00A4223F /* Layouts */,
E41EA46D221715820068EF46 /* Models */,
E450AD8922262B420091BED3 /* Operations */,
E4A83BF2222207BE0098FED6 /* Services */,
E4A83BEC2221F5DD0098FED6 /* Preferences */,
E47E2FE32220AA0700F747E6 /* Layouts */,
E4F6B461221E124700ACF42A /* Models */,
E4F6B45E221E117600ACF42A /* DataSources */,
E407861F2110CE70006887B1 /* Assets.xcassets */,
E408D3B7220DE8CC0006D9BE /* Extensions */,
E4D1B598220BA3C90026F233 /* Resources */,
@ -233,6 +306,9 @@
E4928E0A2218D62A001D4BEA /* CGColor.swift */,
E408D3B5220DD8970006D9BE /* Notification.swift */,
E408D3B8220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift */,
E40FE71A221B904300A4223F /* NSEvent.swift */,
E435E3E1221CD4E200184CFC /* NSFont.swift */,
E435E3E3221CD75D00184CFC /* NSImage.swift */,
);
path = Extensions;
sourceTree = "<group>";
@ -248,22 +324,23 @@
E408D3C3220E138B0006D9BE /* Views */ = {
isa = PBXGroup;
children = (
E408D3C8220E341D0006D9BE /* AlbumItem.swift */,
E47E2FD62220720300F747E6 /* AlbumItemView.swift */,
E47E2FD222205D2500F747E6 /* MainWindow.swift */,
);
path = Views;
sourceTree = "<group>";
};
E40FE717221B48CE00A4223F /* Layouts */ = {
isa = PBXGroup;
children = (
E40FE718221B48E300A4223F /* AlbumViewLayout.swift */,
);
path = Layouts;
sourceTree = "<group>";
};
E41B22BE21FB6B3300D544F6 /* Frameworks */ = {
isa = PBXGroup;
children = (
E450AD97222633920091BED3 /* Alamofire.framework */,
E450AD96222633920091BED3 /* Alamofire.framework.dSYM */,
E450AD8C22262C590091BED3 /* PromiseKit.framework */,
E450AD8E22262C620091BED3 /* PromiseKit.framework.dSYM */,
E450AD8522262AE60091BED3 /* SwiftyJSON.framework */,
E450AD9022262C780091BED3 /* SwiftyJSON.framework.dSYM */,
E421ACA1221F73B8008B2449 /* MediaKeyTap.framework */,
E47E2FCB2220573500F747E6 /* MediaKeyTap.framework.dSYM */,
E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */,
);
name = Frameworks;
@ -320,12 +397,21 @@
path = mpd;
sourceTree = "<group>";
};
E41EA46D221715820068EF46 /* Models */ = {
E450AD8922262B420091BED3 /* Operations */ = {
isa = PBXGroup;
children = (
E41EA46E221715910068EF46 /* Preferences.swift */,
E450AD8A22262B590091BED3 /* AlbumArtOperation.swift */,
E450AD9422262DF10091BED3 /* AlbumArtOperations.swift */,
);
path = Models;
path = Operations;
sourceTree = "<group>";
};
E47E2FE32220AA0700F747E6 /* Layouts */ = {
isa = PBXGroup;
children = (
E47E2FE42220AA0700F747E6 /* AlbumViewLayout.swift */,
);
path = Layouts;
sourceTree = "<group>";
};
E4A642DB220912FA00067D21 /* MPDClient */ = {
@ -339,6 +425,32 @@
path = MPDClient;
sourceTree = "<group>";
};
E4A83BEC2221F5DD0098FED6 /* Preferences */ = {
isa = PBXGroup;
children = (
E4A83BED2221F5E60098FED6 /* Controllers */,
);
path = Preferences;
sourceTree = "<group>";
};
E4A83BED2221F5E60098FED6 /* Controllers */ = {
isa = PBXGroup;
children = (
E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */,
E4A83BEE2221F8CF0098FED6 /* AlbumArtPrefsController.swift */,
E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */,
);
path = Controllers;
sourceTree = "<group>";
};
E4A83BF2222207BE0098FED6 /* Services */ = {
isa = PBXGroup;
children = (
E4A83BF3222207D50098FED6 /* AlbumArtService.swift */,
);
path = Services;
sourceTree = "<group>";
};
E4D1B594220BA2490026F233 /* Models */ = {
isa = PBXGroup;
children = (
@ -361,11 +473,12 @@
E4D1B597220BA3A20026F233 /* Controllers */ = {
isa = PBXGroup;
children = (
E47E2FD4222071FD00F747E6 /* AlbumViewItem.swift */,
E408D3C1220E134F0006D9BE /* AlbumViewController.swift */,
E41EA46B221636AF0068EF46 /* PreferencesViewController.swift */,
E4E8CC932206097F0024217A /* NotificationsController.swift */,
E4E8CC912204F4B80024217A /* QueueViewController.swift */,
E465049921E94DF500A70F4C /* WindowController.swift */,
E47E2FD022205C4600F747E6 /* MainSplitViewController.swift */,
);
path = Controllers;
sourceTree = "<group>";
@ -374,11 +487,31 @@
isa = PBXGroup;
children = (
E40786212110CE70006887B1 /* Main.storyboard */,
E408D3C9220E341D0006D9BE /* AlbumItem.xib */,
E408D3C9220E341D0006D9BE /* AlbumViewItem.xib */,
);
path = Resources;
sourceTree = "<group>";
};
E4F6B45E221E117600ACF42A /* DataSources */ = {
isa = PBXGroup;
children = (
E4F6B45F221E119B00ACF42A /* QueueDataSource.swift */,
E4F6B466221E233200ACF42A /* AlbumDataSource.swift */,
);
path = DataSources;
sourceTree = "<group>";
};
E4F6B461221E124700ACF42A /* Models */ = {
isa = PBXGroup;
children = (
E47E2FDC2220A6D100F747E6 /* Time.swift */,
E40F41F2221EDE27004B6CB8 /* Preferences.swift */,
E4F6B462221E125900ACF42A /* SongItem.swift */,
E450AD7D222620A10091BED3 /* AlbumItem.swift */,
);
path = Models;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -390,6 +523,7 @@
E40786152110CE6E006887B1 /* Frameworks */,
E40786162110CE6E006887B1 /* Resources */,
E41B22C221FB6C3300D544F6 /* Embed Libraries */,
E421AC9B221F7319008B2449 /* CopyFiles */,
);
buildRules = (
);
@ -468,7 +602,7 @@
};
};
buildConfigurationList = E40786132110CE6E006887B1 /* Build configuration list for PBXProject "Persephone" */;
compatibilityVersion = "Xcode 9.3";
compatibilityVersion = "Xcode 10.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
@ -492,11 +626,15 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E450AD9122262C780091BED3 /* SwiftyJSON.framework.dSYM in Resources */,
E42A8F3B22176D6400A13ED9 /* LICENSE.md in Resources */,
E450AD98222633920091BED3 /* Alamofire.framework.dSYM in Resources */,
E40786202110CE70006887B1 /* Assets.xcassets in Resources */,
E42A8F3C22176D6400A13ED9 /* README.md in Resources */,
E408D3CB220E341D0006D9BE /* AlbumItem.xib in Resources */,
E450AD8F22262C620091BED3 /* PromiseKit.framework.dSYM in Resources */,
E408D3CB220E341D0006D9BE /* AlbumViewItem.xib in Resources */,
E40786232110CE70006887B1 /* Main.storyboard in Resources */,
E47E2FCC2220573500F747E6 /* MediaKeyTap.framework.dSYM in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -521,25 +659,41 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E4A83BEF2221F8CF0098FED6 /* AlbumArtPrefsController.swift in Sources */,
E4F6B467221E233200ACF42A /* AlbumDataSource.swift in Sources */,
E408D3C2220E134F0006D9BE /* AlbumViewController.swift in Sources */,
E40FE71B221B904300A4223F /* NSEvent.swift in Sources */,
E4928E0B2218D62A001D4BEA /* CGColor.swift in Sources */,
E4F6B460221E119B00ACF42A /* QueueDataSource.swift in Sources */,
E4A642DA22090CBE00067D21 /* Status.swift in Sources */,
E47E2FD72220720300F747E6 /* AlbumItemView.swift in Sources */,
E450AD9522262DF10091BED3 /* AlbumArtOperations.swift in Sources */,
E450AD7E222620A10091BED3 /* AlbumItem.swift in Sources */,
E4E8CC942206097F0024217A /* NotificationsController.swift in Sources */,
E40FE719221B48E300A4223F /* AlbumViewLayout.swift in Sources */,
E408D3B6220DD8970006D9BE /* Notification.swift in Sources */,
E450AD8B22262B590091BED3 /* AlbumArtOperation.swift in Sources */,
E408D3B9220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift in Sources */,
E4EB2379220F10B8008C70C0 /* Pair.swift in Sources */,
E41EA46F221715910068EF46 /* Preferences.swift in Sources */,
E4F6B463221E125900ACF42A /* SongItem.swift in Sources */,
E4A83BF12221FAA00098FED6 /* PreferencesViewController.swift in Sources */,
E465049A21E94DF500A70F4C /* WindowController.swift in Sources */,
E41B22C621FB932700D544F6 /* MPDClient.swift in Sources */,
E40F41F3221EDE27004B6CB8 /* Preferences.swift in Sources */,
E47E2FDD2220A6D100F747E6 /* Time.swift in Sources */,
E407861C2110CE6E006887B1 /* AppDelegate.swift in Sources */,
E47E2FE52220AA0700F747E6 /* AlbumViewLayout.swift in Sources */,
E47E2FD322205D2500F747E6 /* MainWindow.swift in Sources */,
E47E2FD122205C4600F747E6 /* MainSplitViewController.swift in Sources */,
E4E8CC9A22075D370024217A /* Song.swift in Sources */,
E408D3CA220E341D0006D9BE /* AlbumItem.swift in Sources */,
E41EA46C221636AF0068EF46 /* PreferencesViewController.swift in Sources */,
E41EA46C221636AF0068EF46 /* GeneralPrefsViewController.swift in Sources */,
E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */,
E4A83BF4222207D50098FED6 /* AlbumArtService.swift in Sources */,
E47E2FD5222071FD00F747E6 /* AlbumViewItem.swift in Sources */,
E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */,
E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */,
E4EB237B220F7CF1008C70C0 /* Album.swift in Sources */,
E435E3E4221CD75D00184CFC /* NSImage.swift in Sources */,
E435E3E2221CD4E200184CFC /* NSFont.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -708,6 +862,10 @@
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = BDEE7ZBFZ3;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Carthage/Build/Mac",
);
INFOPLIST_FILE = Persephone/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@ -735,6 +893,10 @@
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = BDEE7ZBFZ3;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Carthage/Build/Mac",
);
INFOPLIST_FILE = Persephone/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",

View File

@ -7,20 +7,33 @@
//
import Cocoa
import MediaKeyTap
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
class AppDelegate: NSObject, NSApplicationDelegate, MediaKeyTapDelegate {
var preferences = Preferences()
var mediaKeyTap: MediaKeyTap?
static let mpdClient = MPDClient(
withDelegate: NotificationsController()
)
@IBAction func fetchCoverArt(_ sender: NSMenuItem) {
NotificationCenter.default.post(
name: Notification.Name("fetchAlbumArt"),
object: self,
userInfo: nil
)
}
func applicationDidFinishLaunching(_ aNotification: Notification) {
connect()
preferences.addObserver(self, forKeyPath: "mpdHost")
preferences.addObserver(self, forKeyPath: "mpdPort")
mediaKeyTap = MediaKeyTap(delegate: self)
mediaKeyTap?.start()
}
func applicationWillTerminate(_ aNotification: Notification) {
@ -42,6 +55,17 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}
}
func handle(mediaKey: MediaKey, event: KeyEvent) {
switch mediaKey {
case .playPause:
AppDelegate.mpdClient.playPause()
case .next, .fastForward:
AppDelegate.mpdClient.nextTrack()
case .previous, .rewind:
AppDelegate.mpdClient.prevTrack()
}
}
func connect() {
AppDelegate.mpdClient.connect(
host: preferences.mpdHostOrDefault,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 KiB

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 KiB

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 670 KiB

After

Width:  |  Height:  |  Size: 613 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 B

View File

@ -9,16 +9,18 @@
import Cocoa
class AlbumViewController: NSViewController,
NSCollectionViewDataSource,
NSCollectionViewDelegate,
NSCollectionViewDelegateFlowLayout {
var albums: [MPDClient.Album] = []
let paddingWidth: CGFloat = 40
let gutterWidth: CGFloat = 20
var dataSource = AlbumDataSource()
override func viewDidLoad() {
super.viewDidLoad()
albumScrollView.postsBoundsChangedNotifications = true
NotificationCenter.default.addObserver(
self,
selector: #selector(updateAlbums(_:)),
@ -32,6 +34,8 @@ class AlbumViewController: NSViewController,
name: Notification.willDisconnect,
object: AppDelegate.mpdClient
)
albumCollectionView.dataSource = dataSource
}
override func viewWillLayout() {
@ -44,30 +48,16 @@ class AlbumViewController: NSViewController,
guard let albums = notification.userInfo?[Notification.albumsKey] as? [MPDClient.Album]
else { return }
self.albums = albums
dataSource.albums = albums.map { AlbumItem(album: $0, coverArt: nil) }
albumCollectionView.reloadData()
}
@objc func clearAlbums(_ notification: Notification) {
self.albums = []
dataSource.albums = []
albumCollectionView.reloadData()
}
func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
return albums.count
}
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
let item = collectionView.makeItem(withIdentifier: .albumItem, for: indexPath)
guard let albumItem = item as? AlbumItem else { return item }
albumItem.view.wantsLayer = true
albumItem.setAlbum(albums[indexPath.item])
return albumItem
}
@IBOutlet var albumScrollView: NSScrollView!
@IBOutlet var albumCollectionView: NSCollectionView!
}

View File

@ -1,5 +1,5 @@
//
// AlbumItem.swift
// AlbumViewItem.swift
// Persephone
//
// Created by Daniel Barber on 2019/2/08.
@ -8,8 +8,9 @@
import Cocoa
class AlbumItem: NSCollectionViewItem {
class AlbumViewItem: NSCollectionViewItem {
var observer: NSKeyValueObservation?
var album: AlbumItem?
override func viewDidLoad() {
super.viewDidLoad()
@ -24,11 +25,35 @@ class AlbumItem: NSCollectionViewItem {
self.setAppearance()
}
}
NotificationCenter.default.addObserver(
self,
selector: #selector(fetchAlbumArt(_:)),
name: Notification.Name("fetchAlbumArt"),
object: nil
)
}
func setAlbum(_ album: MPDClient.Album) {
@objc func fetchAlbumArt(_ notification: Notification) {
guard let album = album else { return }
AlbumArtService.shared.fetchAlbumArt(for: album) { image in
DispatchQueue.main.async { [unowned self] in
self.albumCoverView.image = image
}
}
}
func setAlbum(_ album: AlbumItem) {
self.album = album
albumTitle.stringValue = album.title
albumArtist.stringValue = album.artist
if let coverArt = album.coverArt {
albumCoverView.image = coverArt
} else {
albumCoverView.image = .defaultCoverArt
}
}
func setAppearance() {
@ -42,6 +67,12 @@ class AlbumItem: NSCollectionViewItem {
}
}
@IBAction func playAlbum(_ sender: Any) {
guard let album = album else { return }
AppDelegate.mpdClient.playAlbum(album.album)
}
@IBOutlet var albumCoverView: NSImageView!
@IBOutlet var albumTitle: NSTextField!
@IBOutlet var albumArtist: NSTextField!

View File

@ -0,0 +1,20 @@
//
// MainSplitViewController.swift
// Persephone
//
// Created by Daniel Barber on 2019/2/22.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Cocoa
class MainSplitViewController: NSSplitViewController {
override func keyDown(with event: NSEvent) {
switch event.keyCode {
case NSEvent.keyCodeSpace:
nextResponder?.keyDown(with: event)
default:
super.keyDown(with: event)
}
}
}

View File

@ -26,6 +26,16 @@ class NotificationsController: MPDClientDelegate {
)
}
func didUpdateTime(mpdClient: MPDClient, total: UInt, elapsedMs: UInt) {
sendNotification(
name: Notification.timeChanged,
userInfo: [
Notification.totalTimeKey: total,
Notification.elapsedTimeMsKey: elapsedMs
]
)
}
func didUpdateQueue(mpdClient: MPDClient, queue: [MPDClient.Song]) {
sendNotification(
name: Notification.queueChanged,

View File

@ -8,28 +8,134 @@
import Cocoa
class QueueViewController: NSViewController, NSOutlineViewDataSource, NSOutlineViewDelegate {
var queue: [MPDClient.Song] = []
var queuePos: Int = -1
class QueueViewController: NSViewController,
NSOutlineViewDelegate {
var dataSource = QueueDataSource()
var queueIcon: NSImage? = nil
let systemFontRegular = NSFont.systemFont(ofSize: 13, weight: .regular)
let systemFontBold = NSFont.systemFont(ofSize: 13, weight: .bold)
let playIcon = NSImage(named: "playButton")
let pauseIcon = NSImage(named: "pauseButton")
struct SongItem {
var song: MPDClient.Song
var queuePos: Int
}
@IBOutlet var queueView: NSOutlineView!
override func viewDidLoad() {
super.viewDidLoad()
queueView.columnAutoresizingStyle = .sequentialColumnAutoresizingStyle
setupNotificationObservers()
queueView.dataSource = dataSource
queueView.columnAutoresizingStyle = .sequentialColumnAutoresizingStyle
}
override func keyDown(with event: NSEvent) {
switch event.keyCode {
case NSEvent.keyCodeSpace:
nextResponder?.keyDown(with: event)
default:
super.keyDown(with: event)
}
}
@IBAction func playTrack(_ sender: Any) {
if dataSource.queuePos >= 0 {
AppDelegate.mpdClient.playTrack(queuePos: dataSource.queuePos)
}
}
@objc func stateChanged(_ notification: Notification) {
guard let state = notification.userInfo?[Notification.stateKey] as? MPDClient.Status.State
else { return }
dataSource.setQueueIcon(state)
}
@objc func queueChanged(_ notification: Notification) {
guard let queue = notification.userInfo?[Notification.queueKey] as? [MPDClient.Song]
else { return }
dataSource.updateQueue(queue)
queueView.reloadData()
}
@objc func queuePosChanged(_ notification: Notification) {
guard let queuePos = notification.userInfo?[Notification.queuePosKey] as? Int
else { return }
dataSource.setQueuePos(queuePos)
queueView.reloadData()
}
func outlineView(
_ outlineView: NSOutlineView,
selectionIndexesForProposedSelection proposedSelectionIndexes: IndexSet
) -> IndexSet {
if proposedSelectionIndexes.contains(0) {
return IndexSet()
} else {
return proposedSelectionIndexes
}
}
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
if let songItem = item as? SongItem {
switch tableColumn?.identifier.rawValue {
case "songTitleColumn":
return cellForSongTitle(outlineView, with: songItem)
case "songArtistColumn":
return cellForSongArtist(outlineView, with: songItem)
default:
return nil
}
} else if tableColumn?.identifier.rawValue == "songTitleColumn" {
return cellForQueueHeading(outlineView)
} else {
return nil
}
}
func cellForSongTitle(_ outlineView: NSOutlineView, with songItem: SongItem) -> NSView {
let cellView = outlineView.makeView(
withIdentifier: .queueSongTitle,
owner: self
) as! NSTableCellView
cellView.textField?.stringValue = songItem.song.getTag(.title)
if songItem.isPlaying {
cellView.textField?.font = .systemFontBold
cellView.imageView?.image = dataSource.queueIcon
} else {
cellView.textField?.font = .systemFontRegular
cellView.imageView?.image = nil
}
return cellView
}
func cellForSongArtist(_ outlineView: NSOutlineView, with songItem: SongItem) -> NSView {
let cellView = outlineView.makeView(
withIdentifier: .queueSongArtist,
owner: self
) as! NSTableCellView
cellView.textField?.stringValue = songItem.song.getTag(.artist)
if songItem.isPlaying {
cellView.textField?.font = .systemFontBold
} else {
cellView.textField?.font = .systemFontRegular
}
return cellView
}
func cellForQueueHeading(_ outlineView: NSOutlineView) -> NSView {
let cellView = outlineView.makeView(
withIdentifier: .queueHeading,
owner: self
) as! NSTableCellView
cellView.textField?.stringValue = "QUEUE"
return cellView
}
func setupNotificationObservers() {
NotificationCenter.default.addObserver(
self,
selector: #selector(stateChanged(_:)),
@ -50,168 +156,5 @@ class QueueViewController: NSViewController, NSOutlineViewDataSource, NSOutlineV
name: Notification.queuePosChanged,
object: AppDelegate.mpdClient
)
NotificationCenter.default.addObserver(
self,
selector: #selector(clearQueue(_:)),
name: Notification.willDisconnect,
object: AppDelegate.mpdClient
)
}
@IBAction func playTrack(_ sender: Any) {
guard let view = sender as? NSOutlineView
else { return }
let queuePos = view.selectedRow - 1
if queuePos >= 0 {
AppDelegate.mpdClient.playTrack(queuePos: queuePos)
}
}
@objc func stateChanged(_ notification: Notification) {
guard let state = notification.userInfo?[Notification.stateKey] as? MPDClient.Status.State
else { return }
setQueueIcon(state)
}
@objc func queueChanged(_ notification: Notification) {
guard let queue = notification.userInfo?[Notification.queueKey] as? [MPDClient.Song]
else { return }
self.queue = queue
queueView.reloadData()
}
@objc func queuePosChanged(_ notification: Notification) {
guard let queuePos = notification.userInfo?[Notification.queuePosKey] as? Int
else { return }
let oldSongRowPos = self.queuePos + 1
let newSongRowPos = queuePos + 1
self.queuePos = queuePos
setQueuePos(oldSongRowPos: oldSongRowPos, newSongRowPos: newSongRowPos)
queueView.reloadData(
forRowIndexes: [oldSongRowPos, newSongRowPos],
columnIndexes: [0, 1]
)
}
@objc func clearQueue(_ notification: Notification) {
self.queue = []
queueView.reloadData()
}
func setQueueIcon(_ state: MPDClient.Status.State) {
switch state {
case .playing:
self.queueIcon = playIcon
case .paused:
self.queueIcon = pauseIcon
default:
self.queueIcon = nil
}
}
func setQueuePos(oldSongRowPos: Int, newSongRowPos: Int) {
if oldSongRowPos > 0 {
guard let oldSongRow = queueView.rowView(atRow: oldSongRowPos, makeIfNecessary: true),
let oldSongTitleCell = oldSongRow.view(atColumn: 0) as? NSTableCellView
else { return }
setRowFont(rowView: oldSongRow, font: systemFontRegular)
oldSongTitleCell.imageView?.image = nil
}
guard let songRow = queueView.rowView(atRow: newSongRowPos, makeIfNecessary: true),
let newSongTitleCell = songRow.view(atColumn: 0) as? NSTableCellView
else { return }
setRowFont(rowView: songRow, font: systemFontBold)
newSongTitleCell.imageView?.image = self.queueIcon
}
func setRowFont(rowView: NSTableRowView, font: NSFont) {
guard let songTitleCell = rowView.view(atColumn: 0) as? NSTableCellView,
let songArtistCell = rowView.view(atColumn: 1) as? NSTableCellView
else { return }
songTitleCell.textField?.font = font
songArtistCell.textField?.font = font
}
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
return queue.count + 1
}
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
return false
}
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
if index > 0 {
return SongItem(song: queue[index - 1], queuePos: index - 1)
} else {
return false
}
}
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
if let songItem = item as? SongItem {
switch tableColumn?.identifier.rawValue {
case "songTitleColumn":
let cellView = outlineView.makeView(
withIdentifier: .queueSongTitle,
owner: self
) as! NSTableCellView
cellView.textField?.stringValue = songItem.song.getTag(.title)
return cellView
case "songArtistColumn":
let cellView = outlineView.makeView(
withIdentifier: .queueSongArtist,
owner: self
) as! NSTableCellView
cellView.textField?.stringValue = songItem.song.getTag(.artist)
return cellView
default:
return nil
}
} else {
if tableColumn?.identifier.rawValue == "songTitleColumn" {
let cellView = outlineView.makeView(
withIdentifier: .queueHeading,
owner: self
) as! NSTableCellView
cellView.textField?.stringValue = "QUEUE"
return cellView
} else {
return nil
}
}
}
func outlineView(
_ outlineView: NSOutlineView,
selectionIndexesForProposedSelection proposedSelectionIndexes: IndexSet
) -> IndexSet {
if proposedSelectionIndexes.contains(0) {
return IndexSet()
} else {
return proposedSelectionIndexes
}
}
@IBOutlet var queueView: NSOutlineView!
}

View File

@ -13,8 +13,10 @@ class WindowController: NSWindowController {
case prevTrack, playPause, stop, nextTrack
}
let playIcon = NSImage(named: "playButton")
let pauseIcon = NSImage(named: "pauseButton")
var state: MPDClient.Status.State?
var totalTime: UInt?
var elapsedTimeMs: UInt?
var trackTimer: Timer?
override func windowDidLoad() {
super.windowDidLoad()
@ -26,25 +28,148 @@ class WindowController: NSWindowController {
name: Notification.stateChanged,
object: AppDelegate.mpdClient
)
NotificationCenter.default.addObserver(
self,
selector: #selector(timeChanged(_:)),
name: Notification.timeChanged,
object: AppDelegate.mpdClient
)
trackProgress.font = .timerFont
trackRemaining.font = .timerFont
}
override func keyDown(with event: NSEvent) {
switch event.keyCode {
case NSEvent.keyCodeSpace:
AppDelegate.mpdClient.playPause()
default:
nextResponder?.keyDown(with: event)
}
}
@objc func stateChanged(_ notification: Notification) {
guard let state = notification.userInfo?[Notification.stateKey] as? MPDClient.Status.State
else { return }
setTransportControlState(state)
self.state = state
setTransportControlState()
}
func setTransportControlState(_ state: MPDClient.Status.State) {
func setTransportControlState() {
guard let state = state else { return }
transportControls.setEnabled(state.isOneOf([.playing, .paused]), forSegment: 0)
transportControls.setEnabled(state.isOneOf([.playing, .paused, .stopped]), forSegment: 1)
transportControls.setEnabled(state.isOneOf([.playing, .paused]), forSegment: 2)
transportControls.setEnabled(state.isOneOf([.playing, .paused]), forSegment: 3)
if state.isOneOf([.paused, .stopped, .unknown]) {
transportControls.setImage(playIcon, forSegment: 1)
transportControls.setImage(.playIcon, forSegment: 1)
} else {
transportControls.setImage(pauseIcon, forSegment: 1)
transportControls.setImage(.pauseIcon, forSegment: 1)
}
}
@objc func timeChanged(_ notification: Notification) {
guard let totalTime = notification.userInfo?[Notification.totalTimeKey] as? UInt,
let elapsedTimeMs = notification.userInfo?[Notification.elapsedTimeMsKey] as? UInt
else { return }
self.totalTime = totalTime
self.elapsedTimeMs = elapsedTimeMs
setTrackProgressControls()
}
func setTrackProgressControls() {
guard let totalTime = totalTime,
let elapsedTimeMs = elapsedTimeMs
else { return }
trackProgressBar.isEnabled = [.playing, .paused].contains(state)
trackProgressBar.maxValue = Double(totalTime * 1000)
if state == .playing {
trackTimer?.invalidate()
trackTimer = Timer.scheduledTimer(
timeInterval: 0.25,
target: self,
selector: #selector(updateProgress(_:)),
userInfo: [
"startTime": CACurrentMediaTime(),
"startElapsed": Double(elapsedTimeMs) / 1000
],
repeats: true
)
} else {
trackTimer?.invalidate()
trackProgressBar.integerValue = Int(elapsedTimeMs)
setTimeElapsed()
setTimeRemaining()
}
}
@objc func updateProgress(_ timer: Timer) {
let currentTime = CACurrentMediaTime()
guard let userInfo = timer.userInfo as? Dictionary<String, Any>,
let startTime = userInfo["startTime"] as? Double,
let startElapsed = userInfo["startElapsed"] as? Double
else { return }
let timeDiff = currentTime - startTime
let newElapsedTimeMs = (startElapsed + timeDiff) * 1000
self.elapsedTimeMs = UInt(newElapsedTimeMs)
trackProgressBar.integerValue = Int(newElapsedTimeMs)
setTimeElapsed()
setTimeRemaining()
}
func setTimeElapsed() {
guard let elapsedTimeMs = elapsedTimeMs else { return }
let time = Time(timeInSeconds: Int(elapsedTimeMs) / 1000)
trackProgress.stringValue = time.formattedTime
}
func setTimeRemaining() {
guard let elapsedTimeMs = elapsedTimeMs,
let totalTime = totalTime
else { return }
let time = Time(timeInSeconds: -(Int(totalTime) - Int(elapsedTimeMs) / 1000))
trackRemaining.stringValue = time.formattedTime
}
// TODO: Refactor this using a gesture recognizer
@IBAction func changeTrackProgress(_ sender: NSSlider) {
guard let event = NSApplication.shared.currentEvent else {
return
}
switch event.type {
case .leftMouseDown:
trackTimer?.invalidate()
case .leftMouseDragged:
self.elapsedTimeMs = UInt(sender.integerValue)
setTimeElapsed()
setTimeRemaining()
case .leftMouseUp:
let seekTime = Float(sender.integerValue) / 1000
AppDelegate.mpdClient.seekCurrentSong(timeInSeconds: seekTime)
default:
break
}
}
@ -65,4 +190,8 @@ class WindowController: NSWindowController {
}
@IBOutlet var transportControls: NSSegmentedCell!
@IBOutlet var trackProgress: NSTextField!
@IBOutlet var trackProgressBar: NSSlider!
@IBOutlet var trackRemaining: NSTextField!
}

View File

@ -0,0 +1,27 @@
//
// AlbumDataSource.swift
// Persephone
//
// Created by Daniel Barber on 2019/2/20.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Cocoa
class AlbumDataSource: NSObject, NSCollectionViewDataSource {
var albums: [AlbumItem] = []
func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
return albums.count
}
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
let item = collectionView.makeItem(withIdentifier: .albumViewItem, for: indexPath)
guard let albumViewItem = item as? AlbumViewItem else { return item }
albumViewItem.view.wantsLayer = true
albumViewItem.setAlbum(albums[indexPath.item])
return albumViewItem
}
}

View File

@ -0,0 +1,65 @@
//
// QueueDataSource.swift
// Persephone
//
// Created by Daniel Barber on 2019/2/20.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Cocoa
class QueueDataSource: NSObject, NSOutlineViewDataSource {
var queue: [SongItem] = []
var queuePos: Int = -1
var queueIcon: NSImage? = nil
let playIcon = NSImage(named: "playButton")
let pauseIcon = NSImage(named: "pauseButton")
func updateQueue(_ queue: [MPDClient.Song]) {
self.queue = queue.enumerated().map { index, song in
SongItem(song: song, queuePos: index, isPlaying: index == queuePos)
}
}
func setQueuePos(_ queuePos: Int) {
let oldSongRowPos = self.queuePos
let newSongRowPos = queuePos
self.queuePos = queuePos
if oldSongRowPos >= 0 {
queue[oldSongRowPos].isPlaying = false
}
if newSongRowPos >= 0 {
queue[newSongRowPos].isPlaying = true
}
}
func setQueueIcon(_ state: MPDClient.Status.State) {
switch state {
case .playing:
queueIcon = playIcon
case .paused:
queueIcon = pauseIcon
default:
queueIcon = nil
}
}
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
return queue.count + 1
}
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
return false
}
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
if index > 0 {
return queue[index - 1]
} else {
return false
}
}
}

View File

@ -0,0 +1,13 @@
//
// NSEvent.swift
// Persephone
//
// Created by Daniel Barber on 2019/2/18.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Cocoa
extension NSEvent {
static let keyCodeSpace: UInt16 = 49
}

View File

@ -0,0 +1,16 @@
//
// NSFont.swift
// Persephone
//
// Created by Daniel Barber on 2019/2/19.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Cocoa
extension NSFont {
static let systemFontRegular = systemFont(ofSize: 13, weight: .regular)
static let systemFontBold = systemFont(ofSize: 13, weight: .bold)
static let timerFont = monospacedDigitSystemFont(ofSize: 13, weight: .regular)
}

View File

@ -0,0 +1,16 @@
//
// NSImage.swift
// Persephone
//
// Created by Daniel Barber on 2019/2/19.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Cocoa
extension NSImage {
static let playIcon = NSImage(named: "playButton")
static let pauseIcon = NSImage(named: "pauseButton")
static let defaultCoverArt = NSImage(named: "blankAlbum")
}

View File

@ -16,5 +16,5 @@ extension NSUserInterfaceItemIdentifier {
static let queueSongArtist = NSUserInterfaceItemIdentifier("songArtistCell")
static let queueSongTitle = NSUserInterfaceItemIdentifier("songTitleCell")
static let albumItem = NSUserInterfaceItemIdentifier("AlbumItem")
static let albumViewItem = NSUserInterfaceItemIdentifier("AlbumViewItem")
}

View File

@ -9,16 +9,19 @@
import Foundation
extension Notification {
static let didConnect = Notification.Name("MPDClientDidConnect")
static let willDisconnect = Notification.Name("MPDClientWillDisconnect")
static let didConnect = Name("MPDClientDidConnect")
static let willDisconnect = Name("MPDClientWillDisconnect")
static let stateChanged = Notification.Name("MPDClientStateChanged")
static let queueChanged = Notification.Name("MPDClientQueueChanged")
static let queuePosChanged = Notification.Name("MPDClientQueuePosChanged")
static let loadedAlbums = Notification.Name("MPDClientLoadedAlbums")
static let stateChanged = Name("MPDClientStateChanged")
static let timeChanged = Name("MPDClientTimeChanged")
static let queueChanged = Name("MPDClientQueueChanged")
static let queuePosChanged = Name("MPDClientQueuePosChanged")
static let loadedAlbums = Name("MPDClientLoadedAlbums")
static let stateKey = "state"
static let queueKey = "queue"
static let queuePosKey = "song"
static let albumsKey = "albums"
static let totalTimeKey = "totalTime"
static let elapsedTimeMsKey = "elapsedTimeMs"
}

View File

@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>0.8.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSApplicationCategoryType</key>

View File

@ -64,6 +64,7 @@ class MPDClient {
self.delegate?.didConnect(mpdClient: self)
self.delegate?.didUpdateState(mpdClient: self, state: self.status!.state)
self.delegate?.didUpdateTime(mpdClient: self, total: self.status!.totalTime, elapsedMs: self.status!.elapsedTimeMs)
self.delegate?.didUpdateQueue(mpdClient: self, queue: self.queue)
self.delegate?.didUpdateQueuePos(mpdClient: self, song: self.status!.song)
}
@ -119,6 +120,35 @@ class MPDClient {
idle()
}
func seekCurrentSong(timeInSeconds: Float) {
noIdle()
commandQueue.async { [unowned self] in
mpd_run_seek_current(self.connection, timeInSeconds, false)
}
idle()
}
func playAlbum(_ album: Album) {
noIdle()
commandQueue.async { [unowned self] in
var songs: [Song] = []
mpd_run_clear(self.connection)
mpd_search_db_songs(self.connection, true)
mpd_search_add_tag_constraint(self.connection, MPD_OPERATOR_DEFAULT, MPD_TAG_ALBUM, album.title)
mpd_search_add_tag_constraint(self.connection, MPD_OPERATOR_DEFAULT, MPD_TAG_ALBUM_ARTIST, album.artist)
mpd_search_commit(self.connection)
while let mpdSong = mpd_recv_song(self.connection) {
songs.append(Song(mpdSong))
}
for song in songs {
mpd_run_add(self.connection, song.uri)
}
mpd_run_play_pos(self.connection, 0)
}
idle()
}
func queueCommand(command: Command) {
guard isConnected else { return }
@ -225,8 +255,8 @@ class MPDClient {
func idle() {
commandQueue.async { [unowned self] in
mpd_send_idle(self.connection)
let result = mpd_recv_idle(self.connection, true)
let result = mpd_recv_idle(self.connection, true)
self.handleIdleResult(result)
}
}
@ -240,8 +270,12 @@ class MPDClient {
}
if mpdIdle.contains(.player) {
self.fetchStatus()
self.delegate?.didUpdateState(mpdClient: self, state: self.status!.state)
self.delegate?.didUpdateQueuePos(mpdClient: self, song: self.status!.song)
if let status = self.status {
self.delegate?.didUpdateState(mpdClient: self, state: status.state)
self.delegate?.didUpdateTime(mpdClient: self, total: status.totalTime, elapsedMs: status.elapsedTimeMs)
self.delegate?.didUpdateQueuePos(mpdClient: self, song: status.song)
}
}
if !mpdIdle.isEmpty {
self.idle()

View File

@ -41,6 +41,10 @@ extension MPDClient {
mpd_song_free(mpdSong)
}
var uri: UnsafePointer<Int8> {
return mpd_song_get_uri(mpdSong)
}
func getTag(_ tagType: TagType) -> String {
let mpdTagType = mpd_tag_type(rawValue: Int32(tagType.rawValue))

View File

@ -34,9 +34,20 @@ extension MPDClient {
return State(rawValue: UInt(mpdState.rawValue))!
}
var totalTime: UInt {
let mpdTotalTime = mpd_status_get_total_time(mpdStatus)
return UInt(mpdTotalTime)
}
var elapsedTimeMs: UInt {
let mpdElapsedTimeMs = mpd_status_get_elapsed_ms(mpdStatus)
return UInt(mpdElapsedTimeMs)
}
var song: Int {
return Int(mpd_status_get_song_pos(mpdStatus))
}
}
}

View File

@ -13,7 +13,10 @@ protocol MPDClientDelegate {
func willDisconnect(mpdClient: MPDClient)
func didUpdateState(mpdClient: MPDClient, state: MPDClient.Status.State)
func didUpdateTime(mpdClient: MPDClient, total: UInt, elapsedMs: UInt)
func didUpdateQueue(mpdClient: MPDClient, queue: [MPDClient.Song])
func didUpdateQueuePos(mpdClient: MPDClient, song: Int)
func didLoadAlbums(mpdClient: MPDClient, albums: [MPDClient.Album])
}

View File

@ -0,0 +1,22 @@
//
// AlbumItem.swift
// Persephone
//
// Created by Daniel Barber on 2019/2/26.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Cocoa
struct AlbumItem {
var album: MPDClient.Album
var coverArt: NSImage?
var title: String {
return album.title
}
var artist: String {
return album.artist
}
}

View File

@ -0,0 +1,15 @@
//
// SongItem.swift
// Persephone
//
// Created by Daniel Barber on 2019/2/20.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Foundation
struct SongItem {
var song: MPDClient.Song
var queuePos: Int
var isPlaying: Bool
}

View File

@ -0,0 +1,28 @@
//
// Time.swift
// Persephone
//
// Created by Daniel Barber on 2019/2/19.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Foundation
struct Time {
let timeInSeconds: Int
var formattedTime: String {
let formatter = DateComponentsFormatter()
if timeInSeconds >= 3600 {
formatter.allowedUnits = [.second, .minute, .hour]
} else {
formatter.allowedUnits = [.second, .minute]
}
formatter.zeroFormattingBehavior = .pad
formatter.unitsStyle = .positional
return formatter.string(from: TimeInterval(timeInSeconds))!
}
}

View File

@ -0,0 +1,13 @@
//
// File.swift
// Persephone
//
// Created by Daniel Barber on 2019/2/26.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Cocoa
class AlbumArtOperation: Operation {
}

View File

@ -0,0 +1,20 @@
//
// AlbumArtOperationQueue.swift
// Persephone
//
// Created by Daniel Barber on 2019/2/26.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Cocoa
class AlbumArtOperations {
static let shared = AlbumArtOperations()
lazy var fetchAlbumArtQueue: OperationQueue = {
var queue = OperationQueue()
queue.name = "Fetch Album Art queue"
queue.maxConcurrentOperationCount = 1
return queue
}()
}

View File

@ -0,0 +1,17 @@
//
// AlbumArtPrefsController.swift
// Persephone
//
// Created by Daniel Barber on 2019/2/23.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Cocoa
class AlbumArtPrefsController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
preferredContentSize = NSMakeSize(view.frame.size.width, view.frame.size.height)
}
}

View File

@ -8,7 +8,7 @@
import Cocoa
class PreferencesViewController: NSViewController {
class GeneralPrefsViewController: NSViewController {
var preferences = Preferences()
override func viewDidLoad() {
@ -21,6 +21,12 @@ class PreferencesViewController: NSViewController {
if let mpdPort = preferences.mpdPort {
mpdPortField.stringValue = "\(mpdPort)"
}
preferredContentSize = NSMakeSize(view.frame.size.width, view.frame.size.height)
}
override func viewDidAppear() {
super.viewDidAppear()
}
@IBAction func updateMpdHost(_ sender: NSTextField) {

View File

@ -0,0 +1,58 @@
//
// PreferencesController.swift
// Persephone
//
// Created by Daniel Barber on 2019/2/23.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Cocoa
class PreferencesViewController: NSTabViewController {
private lazy var tabViewSizes: [String : NSSize] = [:]
override func viewDidLoad() {
if let viewController = self.tabViewItems.first?.viewController, let title = viewController.title {
tabViewSizes[title] = viewController.view.frame.size
}
super.viewDidLoad()
}
override func transition(from fromViewController: NSViewController, to toViewController: NSViewController, options: NSViewController.TransitionOptions, completionHandler completion: (() -> Void)?) {
NSAnimationContext.runAnimationGroup({ context in
context.duration = 0.5
self.updateWindowFrameAnimated(viewController: toViewController)
super.transition(
from: fromViewController,
to: toViewController,
options: [.crossfade, .allowUserInteraction],
completionHandler: completion
)
}, completionHandler: nil)
}
func updateWindowFrameAnimated(viewController: NSViewController) {
guard let title = viewController.title, let window = view.window
else { return }
let contentSize: NSSize
if tabViewSizes.keys.contains(title) {
contentSize = tabViewSizes[title]!
} else {
contentSize = viewController.view.frame.size
tabViewSizes[title] = contentSize
}
let newWindowSize = window.frameRect(forContentRect: NSRect(origin: NSPoint.zero, size: contentSize)).size
var frame = window.frame
frame.origin.y += frame.height
frame.origin.y -= newWindowSize.height
frame.size = newWindowSize
window.animator().setFrame(frame, display: false)
}
}

View File

@ -6,10 +6,10 @@
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="AlbumItem" customModule="Persephone" customModuleProvider="target">
<customObject id="-2" userLabel="File's Owner" customClass="AlbumViewItem" customModule="Persephone" customModuleProvider="target">
<connections>
<outlet property="albumArtist" destination="5Uu-j1-qyT" id="2Et-tX-InT"/>
<outlet property="albumCoverView" destination="Kfb-8f-ean" id="ZAL-jD-lHj"/>
<outlet property="albumCoverView" destination="Kfb-8f-ean" id="CXx-gB-gz8"/>
<outlet property="albumTitle" destination="KEh-NL-c2W" id="SI3-hm-H2B"/>
<outlet property="imageView" destination="Kfb-8f-ean" id="Ur0-hX-wJm"/>
<outlet property="view" destination="Hz6-mo-xeY" id="v7W-XA-Emc"/>
@ -17,7 +17,7 @@
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView id="Hz6-mo-xeY">
<customView id="Hz6-mo-xeY" customClass="AlbumItemView" customModule="Persephone" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="128" height="167"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
@ -41,24 +41,45 @@
<rect key="frame" x="0.0" y="39" width="128" height="128"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyUpOrDown" image="blankAlbum" id="FsA-JX-BFh"/>
</imageView>
<button hidden="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="n8W-do-HyG">
<rect key="frame" x="43" y="81" width="42" height="43"/>
<constraints>
<constraint firstAttribute="height" constant="42" id="XXC-YE-Ego"/>
<constraint firstAttribute="width" constant="42" id="zcR-GT-zym"/>
</constraints>
<buttonCell key="cell" type="inline" bezelStyle="inline" image="playButtonLarge" imagePosition="only" alignment="center" lineBreakMode="truncatingTail" state="on" borderStyle="border" inset="2" id="T1p-LZ-RpJ">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="smallSystemBold"/>
</buttonCell>
<connections>
<action selector="playAlbum:" target="-2" id="gNt-Rn-kte"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="5Uu-j1-qyT" firstAttribute="trailing" secondItem="KEh-NL-c2W" secondAttribute="trailing" id="64z-uz-4nY"/>
<constraint firstAttribute="bottom" secondItem="KEh-NL-c2W" secondAttribute="bottom" constant="18" id="8Kg-1r-wNp"/>
<constraint firstItem="Kfb-8f-ean" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" id="JMi-4i-dgs"/>
<constraint firstAttribute="trailing" secondItem="Kfb-8f-ean" secondAttribute="trailing" id="KQC-Wz-Bsg"/>
<constraint firstItem="n8W-do-HyG" firstAttribute="centerX" secondItem="KEh-NL-c2W" secondAttribute="centerX" id="Kf1-ws-d4q"/>
<constraint firstItem="5Uu-j1-qyT" firstAttribute="leading" secondItem="KEh-NL-c2W" secondAttribute="leading" id="MUo-0i-fX9"/>
<constraint firstItem="Kfb-8f-ean" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" id="Qbk-jx-zAi"/>
<constraint firstItem="KEh-NL-c2W" firstAttribute="trailing" secondItem="Kfb-8f-ean" secondAttribute="trailing" id="U0w-G4-ggX"/>
<constraint firstItem="KEh-NL-c2W" firstAttribute="leading" secondItem="Kfb-8f-ean" secondAttribute="leading" id="V8r-Rc-Dx7"/>
<constraint firstAttribute="bottom" secondItem="5Uu-j1-qyT" secondAttribute="bottom" id="gci-4h-pDZ"/>
<constraint firstItem="n8W-do-HyG" firstAttribute="centerY" secondItem="Kfb-8f-ean" secondAttribute="centerY" id="pgP-oA-Nxa"/>
<constraint firstAttribute="bottom" secondItem="Kfb-8f-ean" secondAttribute="bottom" constant="39" id="sid-zJ-YMA"/>
</constraints>
<connections>
<outlet property="imageView" destination="Kfb-8f-ean" id="T7Z-En-dU3"/>
<outlet property="playButton" destination="n8W-do-HyG" id="Xw0-iI-svx"/>
</connections>
<point key="canvasLocation" x="-22" y="125.5"/>
</customView>
<collectionViewItem id="Qgu-aI-55A" customClass="AlbumItem" customModule="Persephone" customModuleProvider="target"/>
<collectionViewItem id="Qgu-aI-55A" customClass="AlbumViewItem" customModule="Persephone" customModuleProvider="target"/>
</objects>
<resources>
<image name="blankAlbum" width="128" height="128"/>
<image name="playButtonLarge" width="22" height="22"/>
</resources>
</document>

View File

@ -62,582 +62,6 @@
</items>
</menu>
</menuItem>
<menuItem title="File" id="dMs-cI-mzQ">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="File" id="bib-Uj-vzu">
<items>
<menuItem title="New" keyEquivalent="n" id="Was-JA-tGl">
<connections>
<action selector="newDocument:" target="Ady-hI-5gd" id="4Si-XN-c54"/>
</connections>
</menuItem>
<menuItem title="Open…" keyEquivalent="o" id="IAo-SY-fd9">
<connections>
<action selector="openDocument:" target="Ady-hI-5gd" id="bVn-NM-KNZ"/>
</connections>
</menuItem>
<menuItem title="Open Recent" id="tXI-mr-wws">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Open Recent" systemMenu="recentDocuments" id="oas-Oc-fiZ">
<items>
<menuItem title="Clear Menu" id="vNY-rz-j42">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="clearRecentDocuments:" target="Ady-hI-5gd" id="Daa-9d-B3U"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="m54-Is-iLE"/>
<menuItem title="Close" keyEquivalent="w" id="DVo-aG-piG">
<connections>
<action selector="performClose:" target="Ady-hI-5gd" id="HmO-Ls-i7Q"/>
</connections>
</menuItem>
<menuItem title="Save…" keyEquivalent="s" id="pxx-59-PXV">
<connections>
<action selector="saveDocument:" target="Ady-hI-5gd" id="teZ-XB-qJY"/>
</connections>
</menuItem>
<menuItem title="Save As…" keyEquivalent="S" id="Bw7-FT-i3A">
<connections>
<action selector="saveDocumentAs:" target="Ady-hI-5gd" id="mDf-zr-I0C"/>
</connections>
</menuItem>
<menuItem title="Revert to Saved" keyEquivalent="r" id="KaW-ft-85H">
<connections>
<action selector="revertDocumentToSaved:" target="Ady-hI-5gd" id="iJ3-Pv-kwq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="aJh-i4-bef"/>
<menuItem title="Page Setup…" keyEquivalent="P" id="qIS-W8-SiK">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="runPageLayout:" target="Ady-hI-5gd" id="Din-rz-gC5"/>
</connections>
</menuItem>
<menuItem title="Print…" keyEquivalent="p" id="aTl-1u-JFS">
<connections>
<action selector="print:" target="Ady-hI-5gd" id="qaZ-4w-aoO"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Edit" id="5QF-Oa-p0T">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
<items>
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
<connections>
<action selector="undo:" target="Ady-hI-5gd" id="M6e-cu-g7V"/>
</connections>
</menuItem>
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
<connections>
<action selector="redo:" target="Ady-hI-5gd" id="oIA-Rs-6OD"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
<connections>
<action selector="cut:" target="Ady-hI-5gd" id="YJe-68-I9s"/>
</connections>
</menuItem>
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
<connections>
<action selector="copy:" target="Ady-hI-5gd" id="G1f-GL-Joy"/>
</connections>
</menuItem>
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
<connections>
<action selector="paste:" target="Ady-hI-5gd" id="UvS-8e-Qdg"/>
</connections>
</menuItem>
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteAsPlainText:" target="Ady-hI-5gd" id="cEh-KX-wJQ"/>
</connections>
</menuItem>
<menuItem title="Delete" id="pa3-QI-u2k">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="delete:" target="Ady-hI-5gd" id="0Mk-Ml-PaM"/>
</connections>
</menuItem>
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
<connections>
<action selector="selectAll:" target="Ady-hI-5gd" id="VNm-Mi-diN"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
<menuItem title="Find" id="4EN-yA-p0u">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Find" id="1b7-l0-nxx">
<items>
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="cD7-Qs-BN4"/>
</connections>
</menuItem>
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="WD3-Gg-5AJ"/>
</connections>
</menuItem>
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="NDo-RZ-v9R"/>
</connections>
</menuItem>
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="HOh-sY-3ay"/>
</connections>
</menuItem>
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="U76-nv-p5D"/>
</connections>
</menuItem>
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
<connections>
<action selector="centerSelectionInVisibleArea:" target="Ady-hI-5gd" id="IOG-6D-g5B"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
<items>
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
<connections>
<action selector="showGuessPanel:" target="Ady-hI-5gd" id="vFj-Ks-hy3"/>
</connections>
</menuItem>
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
<connections>
<action selector="checkSpelling:" target="Ady-hI-5gd" id="fz7-VC-reM"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleContinuousSpellChecking:" target="Ady-hI-5gd" id="7w6-Qz-0kB"/>
</connections>
</menuItem>
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleGrammarChecking:" target="Ady-hI-5gd" id="muD-Qn-j4w"/>
</connections>
</menuItem>
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticSpellingCorrection:" target="Ady-hI-5gd" id="2lM-Qi-WAP"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Substitutions" id="9ic-FL-obx">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
<items>
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontSubstitutionsPanel:" target="Ady-hI-5gd" id="oku-mr-iSq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleSmartInsertDelete:" target="Ady-hI-5gd" id="3IJ-Se-DZD"/>
</connections>
</menuItem>
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticQuoteSubstitution:" target="Ady-hI-5gd" id="ptq-xd-QOA"/>
</connections>
</menuItem>
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDashSubstitution:" target="Ady-hI-5gd" id="oCt-pO-9gS"/>
</connections>
</menuItem>
<menuItem title="Smart Links" id="cwL-P1-jid">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticLinkDetection:" target="Ady-hI-5gd" id="Gip-E3-Fov"/>
</connections>
</menuItem>
<menuItem title="Data Detectors" id="tRr-pd-1PS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDataDetection:" target="Ady-hI-5gd" id="R1I-Nq-Kbl"/>
</connections>
</menuItem>
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticTextReplacement:" target="Ady-hI-5gd" id="DvP-Fe-Py6"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Transformations" id="2oI-Rn-ZJC">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
<items>
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="uppercaseWord:" target="Ady-hI-5gd" id="sPh-Tk-edu"/>
</connections>
</menuItem>
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowercaseWord:" target="Ady-hI-5gd" id="iUZ-b5-hil"/>
</connections>
</menuItem>
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="capitalizeWord:" target="Ady-hI-5gd" id="26H-TL-nsh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Speech" id="xrE-MZ-jX0">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
<items>
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="startSpeaking:" target="Ady-hI-5gd" id="654-Ng-kyl"/>
</connections>
</menuItem>
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="stopSpeaking:" target="Ady-hI-5gd" id="dX8-6p-jy9"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Format" id="jxT-CU-nIS">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Format" id="GEO-Iw-cKr">
<items>
<menuItem title="Font" id="Gi5-1S-RQB">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Font" systemMenu="font" id="aXa-aM-Jaq">
<items>
<menuItem title="Show Fonts" keyEquivalent="t" id="Q5e-8K-NDq">
<connections>
<action selector="orderFrontFontPanel:" target="YLy-65-1bz" id="WHr-nq-2xA"/>
</connections>
</menuItem>
<menuItem title="Bold" tag="2" keyEquivalent="b" id="GB9-OM-e27">
<connections>
<action selector="addFontTrait:" target="YLy-65-1bz" id="hqk-hr-sYV"/>
</connections>
</menuItem>
<menuItem title="Italic" tag="1" keyEquivalent="i" id="Vjx-xi-njq">
<connections>
<action selector="addFontTrait:" target="YLy-65-1bz" id="IHV-OB-c03"/>
</connections>
</menuItem>
<menuItem title="Underline" keyEquivalent="u" id="WRG-CD-K1S">
<connections>
<action selector="underline:" target="Ady-hI-5gd" id="FYS-2b-JAY"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="5gT-KC-WSO"/>
<menuItem title="Bigger" tag="3" keyEquivalent="+" id="Ptp-SP-VEL">
<connections>
<action selector="modifyFont:" target="YLy-65-1bz" id="Uc7-di-UnL"/>
</connections>
</menuItem>
<menuItem title="Smaller" tag="4" keyEquivalent="-" id="i1d-Er-qST">
<connections>
<action selector="modifyFont:" target="YLy-65-1bz" id="HcX-Lf-eNd"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kx3-Dk-x3B"/>
<menuItem title="Kern" id="jBQ-r6-VK2">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Kern" id="tlD-Oa-oAM">
<items>
<menuItem title="Use Default" id="GUa-eO-cwY">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useStandardKerning:" target="Ady-hI-5gd" id="6dk-9l-Ckg"/>
</connections>
</menuItem>
<menuItem title="Use None" id="cDB-IK-hbR">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="turnOffKerning:" target="Ady-hI-5gd" id="U8a-gz-Maa"/>
</connections>
</menuItem>
<menuItem title="Tighten" id="46P-cB-AYj">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="tightenKerning:" target="Ady-hI-5gd" id="hr7-Nz-8ro"/>
</connections>
</menuItem>
<menuItem title="Loosen" id="ogc-rX-tC1">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="loosenKerning:" target="Ady-hI-5gd" id="8i4-f9-FKE"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Ligatures" id="o6e-r0-MWq">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Ligatures" id="w0m-vy-SC9">
<items>
<menuItem title="Use Default" id="agt-UL-0e3">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useStandardLigatures:" target="Ady-hI-5gd" id="7uR-wd-Dx6"/>
</connections>
</menuItem>
<menuItem title="Use None" id="J7y-lM-qPV">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="turnOffLigatures:" target="Ady-hI-5gd" id="iX2-gA-Ilz"/>
</connections>
</menuItem>
<menuItem title="Use All" id="xQD-1f-W4t">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useAllLigatures:" target="Ady-hI-5gd" id="KcB-kA-TuK"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Baseline" id="OaQ-X3-Vso">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Baseline" id="ijk-EB-dga">
<items>
<menuItem title="Use Default" id="3Om-Ey-2VK">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unscript:" target="Ady-hI-5gd" id="0vZ-95-Ywn"/>
</connections>
</menuItem>
<menuItem title="Superscript" id="Rqc-34-cIF">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="superscript:" target="Ady-hI-5gd" id="3qV-fo-wpU"/>
</connections>
</menuItem>
<menuItem title="Subscript" id="I0S-gh-46l">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="subscript:" target="Ady-hI-5gd" id="Q6W-4W-IGz"/>
</connections>
</menuItem>
<menuItem title="Raise" id="2h7-ER-AoG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="raiseBaseline:" target="Ady-hI-5gd" id="4sk-31-7Q9"/>
</connections>
</menuItem>
<menuItem title="Lower" id="1tx-W0-xDw">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowerBaseline:" target="Ady-hI-5gd" id="OF1-bc-KW4"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="Ndw-q3-faq"/>
<menuItem title="Show Colors" keyEquivalent="C" id="bgn-CT-cEk">
<connections>
<action selector="orderFrontColorPanel:" target="Ady-hI-5gd" id="mSX-Xz-DV3"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="iMs-zA-UFJ"/>
<menuItem title="Copy Style" keyEquivalent="c" id="5Vv-lz-BsD">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="copyFont:" target="Ady-hI-5gd" id="GJO-xA-L4q"/>
</connections>
</menuItem>
<menuItem title="Paste Style" keyEquivalent="v" id="vKC-jM-MkH">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteFont:" target="Ady-hI-5gd" id="JfD-CL-leO"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Text" id="Fal-I4-PZk">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Text" id="d9c-me-L2H">
<items>
<menuItem title="Align Left" keyEquivalent="{" id="ZM1-6Q-yy1">
<connections>
<action selector="alignLeft:" target="Ady-hI-5gd" id="zUv-R1-uAa"/>
</connections>
</menuItem>
<menuItem title="Center" keyEquivalent="|" id="VIY-Ag-zcb">
<connections>
<action selector="alignCenter:" target="Ady-hI-5gd" id="spX-mk-kcS"/>
</connections>
</menuItem>
<menuItem title="Justify" id="J5U-5w-g23">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="alignJustified:" target="Ady-hI-5gd" id="ljL-7U-jND"/>
</connections>
</menuItem>
<menuItem title="Align Right" keyEquivalent="}" id="wb2-vD-lq4">
<connections>
<action selector="alignRight:" target="Ady-hI-5gd" id="r48-bG-YeY"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="4s2-GY-VfK"/>
<menuItem title="Writing Direction" id="H1b-Si-o9J">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Writing Direction" id="8mr-sm-Yjd">
<items>
<menuItem title="Paragraph" enabled="NO" id="ZvO-Gk-QUH">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem id="YGs-j5-SAR">
<string key="title"> Default</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeBaseWritingDirectionNatural:" target="Ady-hI-5gd" id="qtV-5e-UBP"/>
</connections>
</menuItem>
<menuItem id="Lbh-J2-qVU">
<string key="title"> Left to Right</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeBaseWritingDirectionLeftToRight:" target="Ady-hI-5gd" id="S0X-9S-QSf"/>
</connections>
</menuItem>
<menuItem id="jFq-tB-4Kx">
<string key="title"> Right to Left</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeBaseWritingDirectionRightToLeft:" target="Ady-hI-5gd" id="5fk-qB-AqJ"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="swp-gr-a21"/>
<menuItem title="Selection" enabled="NO" id="cqv-fj-IhA">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem id="Nop-cj-93Q">
<string key="title"> Default</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeTextWritingDirectionNatural:" target="Ady-hI-5gd" id="lPI-Se-ZHp"/>
</connections>
</menuItem>
<menuItem id="BgM-ve-c93">
<string key="title"> Left to Right</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeTextWritingDirectionLeftToRight:" target="Ady-hI-5gd" id="caW-Bv-w94"/>
</connections>
</menuItem>
<menuItem id="RB4-Sm-HuC">
<string key="title"> Right to Left</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeTextWritingDirectionRightToLeft:" target="Ady-hI-5gd" id="EXD-6r-ZUu"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="fKy-g9-1gm"/>
<menuItem title="Show Ruler" id="vLm-3I-IUL">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleRuler:" target="Ady-hI-5gd" id="FOx-HJ-KwY"/>
</connections>
</menuItem>
<menuItem title="Copy Ruler" keyEquivalent="c" id="MkV-Pr-PK5">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="copyRuler:" target="Ady-hI-5gd" id="71i-fW-3W2"/>
</connections>
</menuItem>
<menuItem title="Paste Ruler" keyEquivalent="v" id="LVM-kO-fVI">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="pasteRuler:" target="Ady-hI-5gd" id="cSh-wd-qM2"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="View" id="H8h-7b-M4v">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="View" id="HyV-fh-RgO">
<items>
<menuItem title="Show Toolbar" keyEquivalent="t" id="snW-S8-Cw5">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="toggleToolbarShown:" target="Ady-hI-5gd" id="BXY-wc-z0C"/>
</connections>
</menuItem>
<menuItem title="Customize Toolbar…" id="1UK-8n-QPP">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="runToolbarCustomizationPalette:" target="Ady-hI-5gd" id="pQI-g3-MTW"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="hB3-LF-h0Y"/>
<menuItem title="Show Sidebar" keyEquivalent="s" id="kIP-vf-haE">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="toggleSourceList:" target="Ady-hI-5gd" id="iwa-gc-5KM"/>
</connections>
</menuItem>
<menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="toggleFullScreen:" target="Ady-hI-5gd" id="dU3-MA-1Rq"/>
</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">
@ -691,7 +115,7 @@
<scene sceneID="R2V-B0-nI4">
<objects>
<windowController id="B8D-0N-5wS" customClass="WindowController" customModule="Persephone" customModuleProvider="target" sceneMemberID="viewController">
<window key="window" title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="IQv-IB-iLA">
<window key="window" title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="IQv-IB-iLA" customClass="MainWindow" customModule="Persephone" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="207" y="570" width="960" height="560"/>
@ -720,9 +144,55 @@
</segmentedControl>
</toolbarItem>
<toolbarItem implicitItemIdentifier="NSToolbarFlexibleSpaceItem" id="9ol-aR-mzv"/>
<toolbarItem implicitItemIdentifier="D72AC4FE-1D0A-420A-8EA8-F28DBFCE9A8E" explicitItemIdentifier="trackProgress" label="Track progress" paletteLabel="Track progress" id="n52-8S-6kR">
<nil key="toolTip"/>
<size key="minSize" width="50" height="17"/>
<size key="maxSize" width="55" height="17"/>
<textField key="view" horizontalHuggingPriority="251" verticalHuggingPriority="750" preferredMaxLayoutWidth="50" id="kx6-xm-TAN">
<rect key="frame" x="16" y="14" width="55" height="17"/>
<autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" placeholderString="8:88:88" id="g0c-k5-wCA">
<font key="font" metaFont="system"/>
<color key="textColor" name="tertiaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</toolbarItem>
<toolbarItem implicitItemIdentifier="71780196-F7F7-466C-8DED-D4AB25336BD1" explicitItemIdentifier="trackProgressBar" label="Track progress bar" paletteLabel="Track progress bar" id="s1h-EC-nvL">
<nil key="toolTip"/>
<size key="minSize" width="96" height="17"/>
<size key="maxSize" width="540" height="17"/>
<slider key="view" verticalHuggingPriority="750" id="KMy-xf-rmN">
<rect key="frame" x="5" y="14" width="96" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<sliderCell key="cell" controlSize="small" continuous="YES" enabled="NO" state="on" alignment="left" maxValue="100" tickMarkPosition="above" sliderType="linear" id="zAF-pq-e9g"/>
<connections>
<action selector="changeTrackProgress:" target="B8D-0N-5wS" id="Dkh-9b-A5y"/>
</connections>
</slider>
</toolbarItem>
<toolbarItem implicitItemIdentifier="1FD81C0E-71C5-41A5-9EAE-A6B12A08FA13" label="Track remaining" paletteLabel="Track remaining" id="5U7-UV-xn2">
<nil key="toolTip"/>
<size key="minSize" width="58" height="17"/>
<size key="maxSize" width="60" height="17"/>
<textField key="view" horizontalHuggingPriority="251" verticalHuggingPriority="750" preferredMaxLayoutWidth="58" id="9WZ-ij-lrb">
<rect key="frame" x="16" y="14" width="60" height="17"/>
<autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="left" placeholderString="-8:88:88" id="XUa-pD-s5c">
<font key="font" metaFont="system"/>
<color key="textColor" name="tertiaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</toolbarItem>
</allowedToolbarItems>
<defaultToolbarItems>
<toolbarItem reference="p3r-ty-Pxf"/>
<toolbarItem reference="9ol-aR-mzv"/>
<toolbarItem reference="n52-8S-6kR"/>
<toolbarItem reference="s1h-EC-nvL"/>
<toolbarItem reference="5U7-UV-xn2"/>
<toolbarItem reference="9ol-aR-mzv"/>
</defaultToolbarItems>
</toolbar>
<connections>
@ -730,6 +200,9 @@
</connections>
</window>
<connections>
<outlet property="trackProgress" destination="kx6-xm-TAN" id="XDv-Th-Agj"/>
<outlet property="trackProgressBar" destination="KMy-xf-rmN" id="a67-JU-cyQ"/>
<outlet property="trackRemaining" destination="9WZ-ij-lrb" id="0pH-d7-wvD"/>
<outlet property="transportControls" destination="EBk-sD-nG7" id="yOo-58-Fby"/>
<segue destination="fnD-7K-pHK" kind="relationship" relationship="window.shadowedContentViewController" id="fQQ-kB-KVc"/>
</connections>
@ -738,10 +211,10 @@
</objects>
<point key="canvasLocation" x="74" y="264"/>
</scene>
<!--Split View Controller-->
<!--Main Split View Controller-->
<scene sceneID="1Jk-lV-xYK">
<objects>
<splitViewController id="fnD-7K-pHK" sceneMemberID="viewController">
<splitViewController id="fnD-7K-pHK" customClass="MainSplitViewController" customModule="Persephone" customModuleProvider="target" sceneMemberID="viewController">
<splitViewItems>
<splitViewItem holdingPriority="255" behavior="contentList" id="CWo-v7-gd2"/>
<splitViewItem id="y8g-4F-czS"/>
@ -932,7 +405,7 @@
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn identifier="songArtistColumn" width="144" minWidth="128" maxWidth="1000" id="SPM-QP-DX8">
<tableColumn identifier="songArtistColumn" width="144" minWidth="64" maxWidth="1000" id="SPM-QP-DX8">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Artist">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
@ -949,9 +422,8 @@
<rect key="frame" x="204" y="1" width="144" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tBe-Q9-3Rw">
<rect key="frame" x="0.0" y="0.0" width="222" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="tBe-Q9-3Rw">
<rect key="frame" x="0.0" y="0.0" width="149" height="17"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="Ceb-ec-ydU">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@ -959,6 +431,11 @@
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="tBe-Q9-3Rw" firstAttribute="centerY" secondItem="JSk-Vc-Y7e" secondAttribute="centerY" id="Tkg-cb-Bg6"/>
<constraint firstAttribute="trailing" secondItem="tBe-Q9-3Rw" secondAttribute="trailing" constant="-3" id="VhZ-ua-QQX"/>
<constraint firstItem="tBe-Q9-3Rw" firstAttribute="leading" secondItem="JSk-Vc-Y7e" secondAttribute="leading" constant="2" id="cTy-tR-Grg"/>
</constraints>
<connections>
<outlet property="textField" destination="tBe-Q9-3Rw" id="2e6-zi-tKj"/>
</connections>
@ -968,7 +445,6 @@
</tableColumns>
<connections>
<action trigger="doubleAction" selector="playTrack:" target="KIP-rq-4dM" id="opa-6G-OW0"/>
<outlet property="dataSource" destination="KIP-rq-4dM" id="K1Q-7o-xXW"/>
<outlet property="delegate" destination="KIP-rq-4dM" id="60F-6x-bUE"/>
</connections>
</outlineView>
@ -1024,7 +500,6 @@
<collectionViewLayout key="collectionViewLayout" id="YE8-sD-l5P" customClass="AlbumViewLayout" customModule="Persephone" customModuleProvider="target"/>
<color key="primaryBackgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<connections>
<outlet property="dataSource" destination="gPn-fP-LFc" id="2VB-5V-ltv"/>
<outlet property="delegate" destination="gPn-fP-LFc" id="LQ2-Vl-r08"/>
</connections>
</collectionView>
@ -1049,6 +524,7 @@
</view>
<connections>
<outlet property="albumCollectionView" destination="lfq-AB-epE" id="p69-Fs-hCN"/>
<outlet property="albumScrollView" destination="i5f-35-7x8" id="jmd-Sa-Bxt"/>
</connections>
</viewController>
<customObject id="uex-Ws-5X4" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>

View File

@ -0,0 +1,69 @@
//
// AlbumArtService.swift
// Persephone
//
// Created by Daniel Barber on 2019/2/23.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Cocoa
import SwiftyJSON
class AlbumArtService: NSObject {
static var shared = AlbumArtService()
var session = URLSession(configuration: .default)
func fetchAlbumArt(for album: AlbumItem, callBack: @escaping (_ image: NSImage) -> Void) {
let artist = album.artist
let title = album.title
getArtworkFromMusicBrainz(artist: artist, title: title, callBack: callBack)
}
func getArtworkFromMusicBrainz(artist: String, title: String, callBack: @escaping (_ image: NSImage) -> Void) {
if var urlComponents = URLComponents(string: "https://musicbrainz.org/ws/2/release/") {
urlComponents.query = "query=artist:\(artist) AND release:\(title) AND country:US&limit=1&fmt=json"
guard let searchURL = urlComponents.url
else { return }
print(searchURL)
let releaseTask = session.dataTask(with: searchURL) { data, response, error in
if let _ = error {
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
return
}
if let mimeType = httpResponse.mimeType, mimeType == "application/json",
let data = data,
let json = try? JSON(data: data) {
let releaseId = json["releases"][0]["id"]
let coverURL = URLComponents(string: "https://coverartarchive.org/release/\(releaseId)/front")
print(coverURL)
let coverArtTask = self.session.dataTask(with: coverURL!.url!) { data, response, error in
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
return
}
print(httpResponse.mimeType)
if let mimeType = httpResponse.mimeType, mimeType == "image/jpeg",
let data = data,
let coverImage = NSImage(data: data) {
callBack(coverImage)
}
}
coverArtTask.resume()
}
}
releaseTask.resume()
}
}
}

View File

@ -0,0 +1,71 @@
//
// AlbumItemView.swift
// Persephone
//
// Created by Daniel Barber on 2019/2/17.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Cocoa
class AlbumItemView: NSView {
var trackingArea: NSTrackingArea?
override func updateTrackingAreas() {
super.updateTrackingAreas()
guard let albumImageView = imageView else { return }
if let trackingArea = self.trackingArea {
self.removeTrackingArea(trackingArea)
}
let trackingArea = NSTrackingArea(
rect: albumImageView.frame,
options: [.mouseEnteredAndExited, .activeAlways],
owner: self,
userInfo: nil
)
self.trackingArea = trackingArea
addTrackingArea(trackingArea)
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
NotificationCenter.default.addObserver(
self,
selector: #selector(viewWillScroll(_:)),
name: NSScrollView.willStartLiveScrollNotification,
object: nil
)
}
@objc func viewWillScroll(_ notification: Notification) {
hidePlayButton()
}
override func resize(withOldSuperviewSize oldSize: NSSize) {
hidePlayButton()
}
override func mouseEntered(with event: NSEvent) {
showPlayButton()
}
override func mouseExited(with event: NSEvent) {
hidePlayButton()
}
func showPlayButton() {
playButton.isHidden = false
}
func hidePlayButton() {
playButton.isHidden = true
}
@IBOutlet var imageView: NSImageView!
@IBOutlet var playButton: NSButton!
}

View File

@ -0,0 +1,20 @@
//
// MainWindow.swift
// Persephone
//
// Created by Daniel Barber on 2019/2/22.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Cocoa
class MainWindow: NSWindow {
override func keyDown(with event: NSEvent) {
switch event.keyCode {
case NSEvent.keyCodeSpace:
nextResponder?.keyDown(with: event)
default:
super.keyDown(with: event)
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 KiB

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 KiB

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 670 KiB

After

Width:  |  Height:  |  Size: 613 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 B

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

After

Width:  |  Height:  |  Size: 173 KiB

View File

@ -2,6 +2,8 @@
git submodule update --init
carthage update
cd libmpdclient && \
meson . output && \
ninja -C output