1
1
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:
Daniel Barber 2019-06-15 13:17:09 -04:00
parent 8c385d9c88
commit aef5b8534b
Signed by: danbarber
GPG Key ID: 931D8112E0103DD8
11 changed files with 174 additions and 70 deletions

View File

@ -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 */,

View File

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

View File

@ -9,8 +9,7 @@
import AppKit
import ReSwift
class QueueViewController: NSViewController,
NSOutlineViewDelegate {
class QueueViewController: NSViewController {
var dataSource = QueueDataSource()
@IBOutlet var queueView: NSOutlineView!
@ -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)
}
}

View File

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

View File

@ -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()

View File

@ -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))
}
}

View File

@ -33,6 +33,7 @@ extension MPDClient {
case replaceQueue
case appendSong
case removeSong
case moveSongInQueue
// Album commands
case fetchAllAlbums

View File

@ -6,7 +6,7 @@
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Foundation
import AppKit
struct QueueItem: Equatable {
var song: Song

View File

@ -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>

View File

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

View File

@ -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)