| | |
| | | private var dragWindow: NSWindow? |
| | | private var dragImageSize: NSSize = .zero |
| | | private var activePayload = "" |
| | | private weak var hoveredTarget: AppDropTargetReceivingView? |
| | | |
| | | private init() {} |
| | | |
| | |
| | | CATransaction.setDisableActions(true) |
| | | dragLayer.position = contentPoint |
| | | CATransaction.commit() |
| | | updateHoverTarget(at: screenPoint) |
| | | return |
| | | } |
| | | |
| | | guard let dragWindow else { return } |
| | | guard let dragWindow else { |
| | | updateHoverTarget(at: screenPoint) |
| | | return |
| | | } |
| | | let origin = NSPoint( |
| | | x: screenPoint.x - dragImageSize.width / 2, |
| | | y: screenPoint.y - dragImageSize.height / 2 |
| | | ) |
| | | dragWindow.setFrameOrigin(origin) |
| | | updateHoverTarget(at: screenPoint) |
| | | } |
| | | |
| | | func finishDrag(at screenPoint: NSPoint, copy: Bool) { |
| | |
| | | let source = parts.dropFirst().first ?? "" |
| | | pruneDeadTargets() |
| | | |
| | | let hitTarget = targets.values |
| | | .compactMap { target -> (AppDropTargetReceivingView, CGFloat)? in |
| | | guard let view = target.view, |
| | | let frame = view.screenFrame(), |
| | | frame.contains(screenPoint) |
| | | else { return nil } |
| | | return (view, frame.width * frame.height) |
| | | } |
| | | .sorted { $0.1 < $1.1 } |
| | | .first?.0 |
| | | |
| | | if let hitTarget { |
| | | if let hitTarget = dropTarget(at: screenPoint) { |
| | | hitTarget.performDrop(path: path, source: source, copy: copy) |
| | | return |
| | | } |
| | |
| | | } |
| | | |
| | | private func endDragVisuals() { |
| | | hoveredTarget?.appDragHoverChanged(active: false) |
| | | hoveredTarget = nil |
| | | dragLayer?.removeFromSuperlayer() |
| | | dragLayer = nil |
| | | dragLayerHostView = nil |
| | |
| | | private func pruneDeadTargets() { |
| | | targets = targets.filter { $0.value.view != nil } |
| | | emptyTargets = emptyTargets.filter { $0.value.view != nil } |
| | | } |
| | | |
| | | private func updateHoverTarget(at screenPoint: NSPoint) { |
| | | guard hasActiveDrag else { return } |
| | | pruneDeadTargets() |
| | | let nextTarget = dropTarget(at: screenPoint) |
| | | guard nextTarget !== hoveredTarget else { return } |
| | | hoveredTarget?.appDragHoverChanged(active: false) |
| | | nextTarget?.appDragHoverChanged(active: true) |
| | | hoveredTarget = nextTarget |
| | | } |
| | | |
| | | private func dropTarget(at screenPoint: NSPoint) -> AppDropTargetReceivingView? { |
| | | targets.values |
| | | .compactMap { target -> (AppDropTargetReceivingView, CGFloat)? in |
| | | guard let view = target.view, |
| | | let frame = view.screenFrame(), |
| | | frame.contains(screenPoint) |
| | | else { return nil } |
| | | return (view, frame.width * frame.height) |
| | | } |
| | | .sorted { $0.1 < $1.1 } |
| | | .first?.0 |
| | | } |
| | | |
| | | private static func cgImage(from image: NSImage) -> CGImage? { |
| | |
| | | |
| | | protocol AppDropTargetReceivingView: AnyObject { |
| | | func screenFrame() -> NSRect? |
| | | func appDragHoverChanged(active: Bool) |
| | | func performDrop(path: String, source: String, copy: Bool) |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | extension AppDropTargetReceivingView { |
| | | func appDragHoverChanged(active: Bool) {} |
| | | } |
| | | |
| | | extension AppEmptyDropReceivingView where Self: NSView { |
| | | func screenFrame() -> NSRect? { |
| | | guard let window else { return nil } |