From 77896f65b92c756da699e84499b180d35b0f8835 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Thu, 25 Jun 2026 18:52:06 +0800
Subject: [PATCH] Merge dark glass 8.0.5 freeze

---
 src/Apptag/AppDragCoordinator.swift |  175 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 174 insertions(+), 1 deletions(-)

diff --git a/src/Apptag/AppDragCoordinator.swift b/src/Apptag/AppDragCoordinator.swift
index 264706d..c7abcc8 100644
--- a/src/Apptag/AppDragCoordinator.swift
+++ b/src/Apptag/AppDragCoordinator.swift
@@ -21,6 +21,9 @@
     private var dragLayer: CALayer?
     private var normalDragImage: CGImage?
     private var copyDragImage: CGImage?
+    private var shredPreviewActive = false
+    private var shredSliceLayers: [CALayer] = []
+    private var shredGuideLayers: [CALayer] = []
     private var currentCopyMode = false
     private var dragWindow: NSWindow?
     private var dragImageSize: NSSize = .zero
@@ -131,6 +134,7 @@
             dragLayer.position = contentPoint
             CATransaction.commit()
             updateHoverTarget(at: screenPoint)
+            updateRemoveTagPreview(at: screenPoint)
             hoveredTarget?.appDragLocationChanged(screenPoint: screenPoint, copy: currentCopyMode)
             return
         }
@@ -145,6 +149,7 @@
         )
         dragWindow.setFrameOrigin(origin)
         updateHoverTarget(at: screenPoint)
+        updateRemoveTagPreview(at: screenPoint)
         hoveredTarget?.appDragLocationChanged(screenPoint: screenPoint, copy: currentCopyMode)
     }
 
@@ -190,11 +195,17 @@
         currentCopyMode = copy
         CATransaction.begin()
         CATransaction.setDisableActions(true)
-        dragLayer?.contents = copy ? copyDragImage : normalDragImage
+        if shredPreviewActive {
+            let image = currentDragImage
+            shredSliceLayers.forEach { $0.contents = image }
+        } else {
+            dragLayer?.contents = currentDragImage
+        }
         CATransaction.commit()
     }
 
     private func endDragVisuals() {
+        stopShredPreview()
         hoveredTarget?.appDragHoverChanged(active: false)
         hoveredTarget = nil
         dragLayer?.removeFromSuperlayer()
@@ -210,6 +221,10 @@
         dragImageSize = .zero
     }
 
+    private var currentDragImage: CGImage? {
+        currentCopyMode ? copyDragImage : normalDragImage
+    }
+
     private func pruneDeadTargets() {
         targets = targets.filter { $0.value.view != nil }
         emptyTargets = emptyTargets.filter { $0.value.view != nil }
@@ -223,6 +238,157 @@
         hoveredTarget?.appDragHoverChanged(active: false)
         nextTarget?.appDragHoverChanged(active: true)
         hoveredTarget = nextTarget
+    }
+
+    private func updateRemoveTagPreview(at screenPoint: NSPoint) {
+        guard hasActiveDrag else {
+            setShredPreviewActive(false)
+            return
+        }
+        setShredPreviewActive(removeTagPreviewTarget(at: screenPoint) != nil)
+    }
+
+    private func removeTagPreviewTarget(at screenPoint: NSPoint) -> AppEmptyDropReceivingView? {
+        guard dropTarget(at: screenPoint) == nil else { return nil }
+        let parts = activePayload.components(separatedBy: "\n")
+        guard let path = parts.first, !path.isEmpty else { return nil }
+        let source = parts.dropFirst().first ?? ""
+
+        return emptyTargets.values
+            .compactMap { target -> (AppEmptyDropReceivingView, CGFloat)? in
+                guard let view = target.view,
+                      let frame = view.screenFrame(),
+                      frame.contains(screenPoint),
+                      view.canPreviewEmptyDrop(
+                        path: path,
+                        source: source,
+                        screenPoint: screenPoint,
+                        copy: currentCopyMode
+                      )
+                else { return nil }
+                return (view, frame.width * frame.height)
+            }
+            .sorted { $0.1 < $1.1 }
+            .first?.0
+    }
+
+    private func setShredPreviewActive(_ active: Bool) {
+        guard active != shredPreviewActive else { return }
+        if active {
+            startShredPreview()
+        } else {
+            stopShredPreview()
+        }
+    }
+
+    private func startShredPreview() {
+        guard let dragLayer,
+              let image = currentDragImage,
+              !shredPreviewActive,
+              dragImageSize.width > 0,
+              dragImageSize.height > 0
+        else { return }
+
+        shredPreviewActive = true
+        dragLayer.contents = nil
+        shredSliceLayers.removeAll()
+        shredGuideLayers.removeAll()
+
+        let sliceCount = 7
+        let sliceWidth = dragImageSize.width / CGFloat(sliceCount)
+        for index in 0..<sliceCount {
+            let x = CGFloat(index) * sliceWidth
+            let width = index == sliceCount - 1
+                ? dragImageSize.width - x
+                : ceil(sliceWidth)
+            let slice = CALayer()
+            slice.frame = CGRect(x: x, y: 0, width: width, height: dragImageSize.height)
+            slice.contents = image
+            slice.contentsGravity = .resize
+            slice.contentsScale = NSScreen.main?.backingScaleFactor ?? 2
+            slice.contentsRect = CGRect(
+                x: CGFloat(index) / CGFloat(sliceCount),
+                y: 0,
+                width: 1 / CGFloat(sliceCount),
+                height: 1
+            )
+            slice.actions = [
+                "position": NSNull(),
+                "opacity": NSNull(),
+                "transform": NSNull()
+            ]
+            dragLayer.addSublayer(slice)
+            addShredAnimation(to: slice, index: index)
+            shredSliceLayers.append(slice)
+
+            if index > 0 {
+                let guide = CALayer()
+                guide.frame = CGRect(
+                    x: x - 0.5,
+                    y: dragImageSize.height * 0.12,
+                    width: 1,
+                    height: dragImageSize.height * 0.76
+                )
+                guide.backgroundColor = NSColor.white.withAlphaComponent(0.42).cgColor
+                guide.opacity = 0
+                guide.actions = [
+                    "opacity": NSNull(),
+                    "position": NSNull()
+                ]
+                dragLayer.addSublayer(guide)
+                addShredGuideAnimation(to: guide, index: index)
+                shredGuideLayers.append(guide)
+            }
+        }
+    }
+
+    private func stopShredPreview() {
+        guard shredPreviewActive || !shredSliceLayers.isEmpty || !shredGuideLayers.isEmpty else { return }
+        shredPreviewActive = false
+        shredSliceLayers.forEach {
+            $0.removeAllAnimations()
+            $0.removeFromSuperlayer()
+        }
+        shredGuideLayers.forEach {
+            $0.removeAllAnimations()
+            $0.removeFromSuperlayer()
+        }
+        shredSliceLayers.removeAll()
+        shredGuideLayers.removeAll()
+        dragLayer?.contents = currentDragImage
+    }
+
+    private func addShredAnimation(to layer: CALayer, index: Int) {
+        let direction: CGFloat = index.isMultiple(of: 2) ? 1 : -1
+        let travel = min(4.5, max(2.0, dragImageSize.height * 0.06))
+        let drift = min(2.4, max(1.0, dragImageSize.width * 0.025)) * direction
+
+        let y = CAKeyframeAnimation(keyPath: "transform.translation.y")
+        y.values = [0, -travel, travel * 0.28, -travel * 0.52, 0]
+
+        let x = CAKeyframeAnimation(keyPath: "transform.translation.x")
+        x.values = [0, drift, -drift * 0.45, drift * 0.25, 0]
+
+        let opacity = CAKeyframeAnimation(keyPath: "opacity")
+        opacity.values = [1, 0.82, 0.96, 0.88, 1]
+
+        let group = CAAnimationGroup()
+        group.animations = [x, y, opacity]
+        group.duration = 0.46
+        group.repeatCount = .greatestFiniteMagnitude
+        group.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
+        group.beginTime = CACurrentMediaTime() + Double(index) * 0.018
+        layer.add(group, forKey: "removeTagShredPreview")
+    }
+
+    private func addShredGuideAnimation(to layer: CALayer, index: Int) {
+        let opacity = CAKeyframeAnimation(keyPath: "opacity")
+        opacity.values = [0, 0.42, 0.18, 0.35, 0]
+        opacity.duration = 0.46
+        opacity.repeatCount = .greatestFiniteMagnitude
+        opacity.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
+        opacity.beginTime = CACurrentMediaTime() + Double(index) * 0.018
+        layer.add(opacity, forKey: "removeTagShredGuide")
     }
 
     private func dropTarget(at screenPoint: NSPoint) -> AppDropTargetReceivingView? {
@@ -298,6 +464,7 @@
 
 protocol AppEmptyDropReceivingView: AnyObject {
     func screenFrame() -> NSRect?
+    func canPreviewEmptyDrop(path: String, source: String, screenPoint: NSPoint, copy: Bool) -> Bool
     func performEmptyDrop(path: String, source: String, screenPoint: NSPoint, copy: Bool)
 }
 
@@ -325,6 +492,12 @@
     }
 }
 
+extension AppEmptyDropReceivingView {
+    func canPreviewEmptyDrop(path: String, source: String, screenPoint: NSPoint, copy: Bool) -> Bool {
+        true
+    }
+}
+
 struct AppDropTargetView: NSViewRepresentable {
     let targetTag: String
     let onDropApp: (String, String, Bool) -> Void

--
Gitblit v1.9.3