mirror of
https://github.com/danbee/persephone
synced 2025-03-04 08:39:11 +00:00
Drag and drop moves tracks in queue
For some reason we're not seeing the insert indicator, and I can't figure out why.
This commit is contained in:
parent
8c385d9c88
commit
aef5b8534b
@ -112,6 +112,7 @@
|
||||
E4B11BC22275EE410075461B /* AlbumListActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11BC12275EE410075461B /* AlbumListActions.swift */; };
|
||||
E4C8B53C22342005009A20F3 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */; };
|
||||
E4C8B53E22349002009A20F3 /* MPDIdle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C8B53D22349002009A20F3 /* MPDIdle.swift */; };
|
||||
E4D3BFA622B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3BFA522B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift */; };
|
||||
E4E7A6AD22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E7A6AC22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift */; };
|
||||
E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC8F2204EC7F0024217A /* Delegate.swift */; };
|
||||
E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC912204F4B80024217A /* QueueViewController.swift */; };
|
||||
@ -317,6 +318,7 @@
|
||||
E4B11BC12275EE410075461B /* AlbumListActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumListActions.swift; sourceTree = "<group>"; };
|
||||
E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; };
|
||||
E4C8B53D22349002009A20F3 /* MPDIdle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDIdle.swift; sourceTree = "<group>"; };
|
||||
E4D3BFA522B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QueueViewController+NSOutlineViewDelegate.swift"; sourceTree = "<group>"; };
|
||||
E4E7A6AC22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AlbumDetailView+NSTableViewDelegate.swift"; sourceTree = "<group>"; };
|
||||
E4E8CC8F2204EC7F0024217A /* Delegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Delegate.swift; sourceTree = "<group>"; };
|
||||
E4E8CC912204F4B80024217A /* QueueViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueViewController.swift; sourceTree = "<group>"; };
|
||||
@ -694,6 +696,7 @@
|
||||
E47E2FD022205C4600F747E6 /* MainSplitViewController.swift */,
|
||||
E4405191227644340090CD6F /* MPDServerController.swift */,
|
||||
E4E8CC912204F4B80024217A /* QueueViewController.swift */,
|
||||
E4D3BFA522B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift */,
|
||||
E4B11BB52275374B0075461B /* UserNotificationsController.swift */,
|
||||
E465049921E94DF500A70F4C /* WindowController.swift */,
|
||||
);
|
||||
@ -921,6 +924,7 @@
|
||||
E4B11BB62275374B0075461B /* UserNotificationsController.swift in Sources */,
|
||||
E4B11B68226A4FA00075461B /* QueueState.swift in Sources */,
|
||||
E4928E0B2218D62A001D4BEA /* CGColor.swift in Sources */,
|
||||
E4D3BFA622B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift in Sources */,
|
||||
E4405196227879960090CD6F /* MPDActions.swift in Sources */,
|
||||
E4405192227644340090CD6F /* MPDServerController.swift in Sources */,
|
||||
E4C8B53E22349002009A20F3 /* MPDIdle.swift in Sources */,
|
||||
|
||||
@ -0,0 +1,87 @@
|
||||
//
|
||||
// QueueViewController+NSOutlineViewDelegate.swift
|
||||
// Persephone
|
||||
//
|
||||
// Created by Daniel Barber on 2019/6/14.
|
||||
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
|
||||
extension QueueViewController: NSOutlineViewDelegate {
|
||||
func outlineView(
|
||||
_ outlineView: NSOutlineView,
|
||||
selectionIndexesForProposedSelection proposedSelectionIndexes: IndexSet
|
||||
) -> IndexSet {
|
||||
if proposedSelectionIndexes.contains(0) {
|
||||
return IndexSet()
|
||||
} else {
|
||||
return proposedSelectionIndexes
|
||||
}
|
||||
}
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
|
||||
if let queueItem = item as? QueueItem {
|
||||
switch tableColumn?.identifier.rawValue {
|
||||
case "songTitleColumn":
|
||||
return cellForSongTitle(outlineView, with: queueItem)
|
||||
case "songArtistColumn":
|
||||
return cellForSongArtist(outlineView, with: queueItem)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
} else if tableColumn?.identifier.rawValue == "songTitleColumn" {
|
||||
return cellForQueueHeading(outlineView)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func outlineViewSelectionDidChange(_ notification: Notification) {
|
||||
if queueView.selectedRow >= 1 {
|
||||
let queueItem = dataSource.queue[queueView.selectedRow - 1]
|
||||
|
||||
App.store.dispatch(SetSelectedQueueItem(selectedQueueItem: queueItem))
|
||||
} else {
|
||||
App.store.dispatch(SetSelectedQueueItem(selectedQueueItem: nil))
|
||||
}
|
||||
}
|
||||
|
||||
func cellForSongTitle(_ outlineView: NSOutlineView, with queueItem: QueueItem) -> NSView {
|
||||
let cellView = outlineView.makeView(
|
||||
withIdentifier: .queueSongTitle,
|
||||
owner: self
|
||||
) as! QueueSongTitleView
|
||||
|
||||
cellView.setQueueSong(queueItem, queueIcon: dataSource.queueIcon)
|
||||
|
||||
return cellView
|
||||
}
|
||||
|
||||
func cellForSongArtist(_ outlineView: NSOutlineView, with queueItem: QueueItem) -> NSView {
|
||||
let cellView = outlineView.makeView(
|
||||
withIdentifier: .queueSongArtist,
|
||||
owner: self
|
||||
) as! NSTableCellView
|
||||
|
||||
cellView.textField?.stringValue = queueItem.song.artist
|
||||
if queueItem.isPlaying {
|
||||
cellView.textField?.font = .systemFontBold
|
||||
} else {
|
||||
cellView.textField?.font = .systemFontRegular
|
||||
}
|
||||
|
||||
return cellView
|
||||
}
|
||||
|
||||
func cellForQueueHeading(_ outlineView: NSOutlineView) -> NSView {
|
||||
let cellView = outlineView.makeView(
|
||||
withIdentifier: .queueHeading,
|
||||
owner: self
|
||||
) as! NSTableCellView
|
||||
|
||||
cellView.textField?.stringValue = "QUEUE"
|
||||
|
||||
return cellView
|
||||
}
|
||||
}
|
||||
@ -9,8 +9,7 @@
|
||||
import AppKit
|
||||
import ReSwift
|
||||
|
||||
class QueueViewController: NSViewController,
|
||||
NSOutlineViewDelegate {
|
||||
class QueueViewController: NSViewController {
|
||||
var dataSource = QueueDataSource()
|
||||
|
||||
@IBOutlet var queueView: NSOutlineView!
|
||||
@ -23,8 +22,10 @@ class QueueViewController: NSViewController,
|
||||
$0.select { $0.queueState }
|
||||
}
|
||||
|
||||
queueView.dataSource = dataSource
|
||||
// queueView.dataSource = dataSource
|
||||
queueView.columnAutoresizingStyle = .sequentialColumnAutoresizingStyle
|
||||
queueView.registerForDraggedTypes([REORDER_PASTEBOARD_TYPE])
|
||||
queueView.draggingDestinationFeedbackStyle = .regular
|
||||
}
|
||||
|
||||
override func keyDown(with event: NSEvent) {
|
||||
@ -64,81 +65,31 @@ class QueueViewController: NSViewController,
|
||||
App.store.dispatch(MPDRemoveTrack(queuePos: queuePos))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func outlineView(
|
||||
_ outlineView: NSOutlineView,
|
||||
selectionIndexesForProposedSelection proposedSelectionIndexes: IndexSet
|
||||
) -> IndexSet {
|
||||
if proposedSelectionIndexes.contains(0) {
|
||||
return IndexSet()
|
||||
} else {
|
||||
return proposedSelectionIndexes
|
||||
}
|
||||
extension QueueViewController: NSOutlineViewDataSource {
|
||||
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
|
||||
return dataSource.outlineView(outlineView, numberOfChildrenOfItem: item)
|
||||
}
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
|
||||
if let queueItem = item as? QueueItem {
|
||||
switch tableColumn?.identifier.rawValue {
|
||||
case "songTitleColumn":
|
||||
return cellForSongTitle(outlineView, with: queueItem)
|
||||
case "songArtistColumn":
|
||||
return cellForSongArtist(outlineView, with: queueItem)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
} else if tableColumn?.identifier.rawValue == "songTitleColumn" {
|
||||
return cellForQueueHeading(outlineView)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
|
||||
return dataSource.outlineView(outlineView, isItemExpandable: item)
|
||||
}
|
||||
|
||||
func outlineViewSelectionDidChange(_ notification: Notification) {
|
||||
if queueView.selectedRow >= 1 {
|
||||
let queueItem = dataSource.queue[queueView.selectedRow - 1]
|
||||
|
||||
App.store.dispatch(SetSelectedQueueItem(selectedQueueItem: queueItem))
|
||||
} else {
|
||||
App.store.dispatch(SetSelectedQueueItem(selectedQueueItem: nil))
|
||||
}
|
||||
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
|
||||
return dataSource.outlineView(outlineView, child: index, ofItem: item)
|
||||
}
|
||||
|
||||
func cellForSongTitle(_ outlineView: NSOutlineView, with queueItem: QueueItem) -> NSView {
|
||||
let cellView = outlineView.makeView(
|
||||
withIdentifier: .queueSongTitle,
|
||||
owner: self
|
||||
) as! QueueSongTitleView
|
||||
|
||||
cellView.setQueueSong(queueItem, queueIcon: dataSource.queueIcon)
|
||||
|
||||
return cellView
|
||||
func outlineView(_ outlineView: NSOutlineView, pasteboardWriterForItem item: Any) -> NSPasteboardWriting? {
|
||||
return dataSource.outlineView(outlineView, pasteboardWriterForItem: item)
|
||||
}
|
||||
|
||||
func cellForSongArtist(_ outlineView: NSOutlineView, with queueItem: QueueItem) -> NSView {
|
||||
let cellView = outlineView.makeView(
|
||||
withIdentifier: .queueSongArtist,
|
||||
owner: self
|
||||
) as! NSTableCellView
|
||||
|
||||
cellView.textField?.stringValue = queueItem.song.artist
|
||||
if queueItem.isPlaying {
|
||||
cellView.textField?.font = .systemFontBold
|
||||
} else {
|
||||
cellView.textField?.font = .systemFontRegular
|
||||
}
|
||||
|
||||
return cellView
|
||||
func outlineView(_ outlineView: NSOutlineView, validateDrop info: NSDraggingInfo, proposedItem item: Any?, proposedChildIndex index: Int) -> NSDragOperation {
|
||||
return dataSource.outlineView(outlineView, validateDrop: info, proposedItem: item, proposedChildIndex: index)
|
||||
}
|
||||
|
||||
func cellForQueueHeading(_ outlineView: NSOutlineView) -> NSView {
|
||||
let cellView = outlineView.makeView(
|
||||
withIdentifier: .queueHeading,
|
||||
owner: self
|
||||
) as! NSTableCellView
|
||||
|
||||
cellView.textField?.stringValue = "QUEUE"
|
||||
|
||||
return cellView
|
||||
func outlineView(_ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool {
|
||||
return dataSource.outlineView(outlineView, acceptDrop: info, item: item, childIndex: index)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,8 @@
|
||||
|
||||
import AppKit
|
||||
|
||||
let REORDER_PASTEBOARD_TYPE = NSPasteboard.PasteboardType("me.danbarber.persephone")
|
||||
|
||||
class QueueDataSource: NSObject, NSOutlineViewDataSource {
|
||||
var queue: [QueueItem] = []
|
||||
var queueIcon: NSImage? = nil
|
||||
@ -35,7 +37,43 @@ class QueueDataSource: NSObject, NSOutlineViewDataSource {
|
||||
if index > 0 {
|
||||
return queue[index - 1]
|
||||
} else {
|
||||
return false
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, pasteboardWriterForItem item: Any) -> NSPasteboardWriting? {
|
||||
guard let queueItem = item as? QueueItem
|
||||
else { return nil }
|
||||
|
||||
let pbItem = NSPasteboardItem()
|
||||
|
||||
pbItem.setPropertyList(["queuePos": queueItem.queuePos], forType: REORDER_PASTEBOARD_TYPE)
|
||||
|
||||
return pbItem
|
||||
}
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, validateDrop info: NSDraggingInfo, proposedItem item: Any?, proposedChildIndex index: Int) -> NSDragOperation {
|
||||
guard let draggingTypes = info.draggingPasteboard.types,
|
||||
draggingTypes.contains(REORDER_PASTEBOARD_TYPE),
|
||||
index >= 0
|
||||
else { return [] }
|
||||
|
||||
return .move
|
||||
}
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool {
|
||||
var newQueuePos = index - 1
|
||||
|
||||
guard let payload = info.draggingPasteboard.propertyList(forType: REORDER_PASTEBOARD_TYPE) as? [String: Int],
|
||||
let queuePos = payload["queuePos"]
|
||||
else { return false }
|
||||
|
||||
if newQueuePos > queuePos { newQueuePos -= 1 }
|
||||
|
||||
guard queuePos != newQueuePos else { return false }
|
||||
|
||||
App.store.dispatch(MPDMoveSongInQueue(oldQueuePos: queuePos, newQueuePos: newQueuePos))
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,6 +69,12 @@ extension MPDClient {
|
||||
else { return }
|
||||
sendRemoveSong(at: queuePos)
|
||||
|
||||
case .moveSongInQueue:
|
||||
guard let oldQueuePos = userData["oldQueuePos"] as? Int,
|
||||
let newQueuePos = userData["newQueuePos"] as? Int
|
||||
else { return }
|
||||
sendMoveSongInQueue(at: oldQueuePos, to: newQueuePos)
|
||||
|
||||
// Album commands
|
||||
case .fetchAllAlbums:
|
||||
allAlbums()
|
||||
|
||||
@ -30,6 +30,10 @@ extension MPDClient {
|
||||
enqueueCommand(command: .removeSong, userData: ["queuePos": queuePos])
|
||||
}
|
||||
|
||||
func moveSongInQueue(at queuePos: Int, to newQueuePos: Int) {
|
||||
enqueueCommand(command: .moveSongInQueue, userData: ["oldQueuePos": queuePos, "newQueuePos": newQueuePos])
|
||||
}
|
||||
|
||||
func sendPlayTrack(at queuePos: Int) {
|
||||
mpd_run_play_pos(self.connection, UInt32(queuePos))
|
||||
}
|
||||
@ -64,4 +68,8 @@ extension MPDClient {
|
||||
func sendRemoveSong(at queuePos: Int) {
|
||||
mpd_run_delete(self.connection, UInt32(queuePos))
|
||||
}
|
||||
|
||||
func sendMoveSongInQueue(at oldQueuePos: Int, to newQueuePos: Int) {
|
||||
mpd_run_move(self.connection, UInt32(oldQueuePos), UInt32(newQueuePos))
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,6 +33,7 @@ extension MPDClient {
|
||||
case replaceQueue
|
||||
case appendSong
|
||||
case removeSong
|
||||
case moveSongInQueue
|
||||
|
||||
// Album commands
|
||||
case fetchAllAlbums
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AppKit
|
||||
|
||||
struct QueueItem: Equatable {
|
||||
var song: Song
|
||||
|
||||
@ -574,7 +574,7 @@
|
||||
<scene sceneID="QcX-dC-cTZ">
|
||||
<objects>
|
||||
<viewController id="KIP-rq-4dM" customClass="QueueViewController" customModule="Persephone" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<splitView key="view" wantsLayer="YES" dividerStyle="thin" id="84I-w3-Mxl">
|
||||
<splitView key="view" dividerStyle="thin" id="84I-w3-Mxl">
|
||||
<rect key="frame" x="0.0" y="0.0" width="328" height="548"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
@ -713,6 +713,7 @@
|
||||
</tableColumns>
|
||||
<connections>
|
||||
<action trigger="doubleAction" selector="playTrack:" target="KIP-rq-4dM" id="opa-6G-OW0"/>
|
||||
<outlet property="dataSource" destination="KIP-rq-4dM" id="AL2-j1-SVE"/>
|
||||
<outlet property="delegate" destination="KIP-rq-4dM" id="60F-6x-bUE"/>
|
||||
<outlet property="menu" destination="dYA-Jm-eOa" id="9s2-7K-tVx"/>
|
||||
</connections>
|
||||
|
||||
@ -18,6 +18,11 @@ struct MPDPrevTrackAction: Action {}
|
||||
|
||||
struct MPDClearQueue: Action {}
|
||||
|
||||
struct MPDMoveSongInQueue: Action {
|
||||
let oldQueuePos: Int
|
||||
let newQueuePos: Int
|
||||
}
|
||||
|
||||
struct MPDAppendTrack: Action {
|
||||
let song: MPDClient.MPDSong
|
||||
}
|
||||
|
||||
@ -33,6 +33,9 @@ func mpdReducer(action: Action, state: MPDState?) -> MPDState {
|
||||
case is MPDClearQueue:
|
||||
App.mpdClient.clearQueue()
|
||||
|
||||
case let action as MPDMoveSongInQueue:
|
||||
App.mpdClient.moveSongInQueue(at: action.oldQueuePos, to: action.newQueuePos)
|
||||
|
||||
case let action as MPDAppendTrack:
|
||||
App.mpdClient.appendSong(action.song)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user