From 1a7643b2e7e8f9712533e5a68536ee3b23b0804b Mon Sep 17 00:00:00 2001
From: Ariver <ar@MacBook-Air.local>
Date: Mon, 11 May 2026 11:51:02 +0800
Subject: [PATCH] Implement CALayer drag preview for 5.2.0

---
 Apptag/Info.plist               |    4 
 Apptag/AppDragCoordinator.swift |  163 +++++++++++++++++++++++++++++-------------------------
 2 files changed, 90 insertions(+), 77 deletions(-)

diff --git a/Apptag/AppDragCoordinator.swift b/Apptag/AppDragCoordinator.swift
index a8f0b00..16917b3 100644
--- a/Apptag/AppDragCoordinator.swift
+++ b/Apptag/AppDragCoordinator.swift
@@ -1,5 +1,6 @@
 import SwiftUI
 import AppKit
+import QuartzCore
 
 final class AppDragCoordinator {
     static let shared = AppDragCoordinator()
@@ -12,8 +13,11 @@
 
     private var targets: [UUID: DropTarget] = [:]
     private weak var dragHostWindow: NSWindow?
-    private weak var dragPreviewSuperview: NSView?
-    private var dragPreviewView: DragPreviewView?
+    private weak var dragLayerHostView: NSView?
+    private var dragLayer: CALayer?
+    private var normalDragImage: CGImage?
+    private var copyDragImage: CGImage?
+    private var currentCopyMode = false
     private var dragWindow: NSWindow?
     private var dragImageSize: NSSize = .zero
     private var activePayload = ""
@@ -33,56 +37,73 @@
         activePayload = payload
         dragImageSize = image.size
         dragHostWindow = hostWindow
+        currentCopyMode = copy
+        normalDragImage = Self.cgImage(from: image)
+        copyDragImage = Self.cgImage(from: Self.copyBadgeImage(from: image))
+
+        let hostView: NSView
 
         if let contentView = hostWindow?.contentView {
-            let preview = DragPreviewView(image: image, copy: copy, frame: NSRect(origin: .zero, size: image.size))
-            preview.translatesAutoresizingMaskIntoConstraints = true
-            preview.autoresizingMask = []
-            contentView.addSubview(preview, positioned: .above, relativeTo: nil)
-            preview.layer?.zPosition = 1_000_000
-            dragPreviewSuperview = contentView
-            dragPreviewView = preview
-            updateDragLocation(screenPoint)
-            return
+            hostView = contentView
+        } else {
+            let panel = NSPanel(
+                contentRect: NSRect(origin: .zero, size: image.size),
+                styleMask: [.borderless, .nonactivatingPanel],
+                backing: .buffered,
+                defer: false
+            )
+            panel.isOpaque = false
+            panel.backgroundColor = .clear
+            panel.hasShadow = false
+            panel.isFloatingPanel = true
+            panel.hidesOnDeactivate = false
+            panel.ignoresMouseEvents = true
+            panel.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.maximumWindow)))
+            panel.collectionBehavior = [.moveToActiveSpace, .fullScreenAuxiliary, .stationary, .transient, .ignoresCycle]
+            panel.isReleasedWhenClosed = false
+            let contentView = NSView(frame: NSRect(origin: .zero, size: image.size))
+            panel.contentView = contentView
+            dragWindow = panel
+            dragHostWindow = panel
+            hostView = contentView
+            panel.setFrameOrigin(NSPoint(x: screenPoint.x - image.size.width / 2, y: screenPoint.y - image.size.height / 2))
+            panel.orderFrontRegardless()
         }
 
-        let panel = NSPanel(
-            contentRect: NSRect(origin: .zero, size: image.size),
-            styleMask: [.borderless, .nonactivatingPanel],
-            backing: .buffered,
-            defer: false
-        )
-        panel.isOpaque = false
-        panel.backgroundColor = .clear
-        panel.hasShadow = false
-        panel.isFloatingPanel = true
-        panel.hidesOnDeactivate = false
-        panel.ignoresMouseEvents = true
-        panel.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.maximumWindow)))
-        panel.collectionBehavior = [.moveToActiveSpace, .fullScreenAuxiliary, .stationary, .transient, .ignoresCycle]
-        panel.isReleasedWhenClosed = false
+        hostView.wantsLayer = true
+        guard let rootLayer = hostView.layer else { return }
 
-        panel.contentView = DragPreviewView(image: image, copy: copy, frame: NSRect(origin: .zero, size: image.size))
+        let layer = CALayer()
+        layer.bounds = CGRect(origin: .zero, size: image.size)
+        layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
+        layer.contentsGravity = .resizeAspect
+        layer.contentsScale = NSScreen.main?.backingScaleFactor ?? 2
+        layer.contents = copy ? copyDragImage : normalDragImage
+        layer.zPosition = 1_000_000
+        layer.actions = [
+            "position": NSNull(),
+            "contents": NSNull(),
+            "bounds": NSNull(),
+            "opacity": NSNull()
+        ]
 
-        dragWindow = panel
+        rootLayer.addSublayer(layer)
+        dragLayerHostView = hostView
+        dragLayer = layer
         updateDragLocation(screenPoint)
-        panel.setFrame(panel.frame, display: true)
-        panel.orderFrontRegardless()
     }
 
     func updateDragLocation(_ screenPoint: NSPoint, copy: Bool? = nil) {
-        if let dragPreviewView, let dragHostWindow, let contentView = dragPreviewSuperview {
+        if let dragLayer, let dragHostWindow, let hostView = dragLayerHostView {
             if let copy {
-                dragPreviewView.isCopyMode = copy
+                updateCopyMode(copy)
             }
             let windowPoint = dragHostWindow.convertPoint(fromScreen: screenPoint)
-            let contentPoint = contentView.convert(windowPoint, from: nil)
-            dragPreviewView.setFrameOrigin(
-                NSPoint(
-                    x: contentPoint.x - dragImageSize.width / 2,
-                    y: contentPoint.y - dragImageSize.height / 2
-                )
-            )
+            let contentPoint = hostView.convert(windowPoint, from: nil)
+            CATransaction.begin()
+            CATransaction.setDisableActions(true)
+            dragLayer.position = contentPoint
+            CATransaction.commit()
             return
         }
 
@@ -115,53 +136,43 @@
         endDragVisuals()
     }
 
+    private func updateCopyMode(_ copy: Bool) {
+        guard currentCopyMode != copy else { return }
+        currentCopyMode = copy
+        CATransaction.begin()
+        CATransaction.setDisableActions(true)
+        dragLayer?.contents = copy ? copyDragImage : normalDragImage
+        CATransaction.commit()
+    }
+
     private func endDragVisuals() {
-        dragPreviewView?.removeFromSuperview()
-        dragPreviewView = nil
-        dragPreviewSuperview = nil
+        dragLayer?.removeFromSuperlayer()
+        dragLayer = nil
+        dragLayerHostView = nil
         dragHostWindow = nil
+        normalDragImage = nil
+        copyDragImage = nil
+        currentCopyMode = false
         dragWindow?.orderOut(nil)
         dragWindow = nil
         activePayload = ""
         dragImageSize = .zero
     }
-}
 
-private final class DragPreviewView: NSView {
-    private let image: NSImage
-    var isCopyMode: Bool {
-        didSet {
-            if oldValue != isCopyMode {
-                needsDisplay = true
-            }
-        }
+    private static func cgImage(from image: NSImage) -> CGImage? {
+        var rect = NSRect(origin: .zero, size: image.size)
+        return image.cgImage(forProposedRect: &rect, context: nil, hints: [
+            .interpolation: NSImageInterpolation.high
+        ])
     }
 
-    init(image: NSImage, copy: Bool, frame: NSRect) {
-        self.image = image
-        self.isCopyMode = copy
-        super.init(frame: frame)
-        wantsLayer = true
-        layer?.masksToBounds = false
-    }
+    private static func copyBadgeImage(from baseImage: NSImage) -> NSImage {
+        let image = NSImage(size: baseImage.size)
+        image.lockFocus()
+        NSGraphicsContext.current?.imageInterpolation = .high
+        baseImage.draw(in: NSRect(origin: .zero, size: baseImage.size), from: .zero, operation: .sourceOver, fraction: 1)
 
-    required init?(coder: NSCoder) {
-        nil
-    }
-
-    override var isOpaque: Bool { false }
-
-    override func hitTest(_ point: NSPoint) -> NSView? {
-        nil
-    }
-
-    override func draw(_ dirtyRect: NSRect) {
-        NSColor.clear.setFill()
-        dirtyRect.fill()
-        image.draw(in: bounds, from: .zero, operation: .sourceOver, fraction: 1.0)
-
-        guard isCopyMode else { return }
-
+        let bounds = NSRect(origin: .zero, size: baseImage.size)
         let badgeSize = min(bounds.width, bounds.height) * 0.28
         let badgeRect = NSRect(
             x: bounds.maxX - badgeSize - badgeSize * 0.22,
@@ -193,6 +204,8 @@
             ),
             withAttributes: attrs
         )
+        image.unlockFocus()
+        return image
     }
 }
 
diff --git a/Apptag/Info.plist b/Apptag/Info.plist
index 781fedb..cb42184 100644
--- a/Apptag/Info.plist
+++ b/Apptag/Info.plist
@@ -19,9 +19,9 @@
 	<key>CFBundlePackageType</key>
 	<string>APPL</string>
 	<key>CFBundleShortVersionString</key>
-	<string>5.1.12</string>
+	<string>5.2.0</string>
 	<key>CFBundleVersion</key>
-	<string>12</string>
+	<string>13</string>
 	<key>LSMinimumSystemVersion</key>
 	<string>15.0</string>
 	<key>NSHighResolutionCapable</key>

--
Gitblit v1.9.3