From 406c5898d103427193e589b4bbe615d0c739b2c5 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Tue, 02 Jun 2026 19:36:31 +0800
Subject: [PATCH] Prepare 7.8.9 tag navigation drop test package

---
 Apptag/TagNavigationView.swift |  126 ++++++++++++++++++++++++++++++++++++++---
 1 files changed, 116 insertions(+), 10 deletions(-)

diff --git a/Apptag/TagNavigationView.swift b/Apptag/TagNavigationView.swift
index 327ab7a..449895d 100644
--- a/Apptag/TagNavigationView.swift
+++ b/Apptag/TagNavigationView.swift
@@ -18,12 +18,17 @@
     let contentInsets: NSEdgeInsets
     let dragModeActive: Bool
     let draggingItemID: String?
+    let appDragModeActive: Bool
+    let appDropTargetID: String?
     let onActivate: (String) -> Void
     let onHoverChange: (String, Bool) -> Void
     let canReorder: (String) -> Bool
+    let canAcceptAppDrop: (String) -> Bool
     let onReorderBegan: (String) -> Void
     let onReorderMoved: (String, String) -> Void
     let onReorderEnded: () -> Void
+    let onAppDropHoverChange: (String, Bool) -> Void
+    let onAppDrop: (String, String, String, Bool) -> Void
 
     func makeNSView(context: Context) -> TagNavigationHostView {
         let view = TagNavigationHostView()
@@ -33,12 +38,17 @@
             contentInsets: contentInsets,
             dragModeActive: dragModeActive,
             draggingItemID: draggingItemID,
+            appDragModeActive: appDragModeActive,
+            appDropTargetID: appDropTargetID,
             onActivate: onActivate,
             onHoverChange: onHoverChange,
             canReorder: canReorder,
+            canAcceptAppDrop: canAcceptAppDrop,
             onReorderBegan: onReorderBegan,
             onReorderMoved: onReorderMoved,
-            onReorderEnded: onReorderEnded
+            onReorderEnded: onReorderEnded,
+            onAppDropHoverChange: onAppDropHoverChange,
+            onAppDrop: onAppDrop
         )
         return view
     }
@@ -50,12 +60,17 @@
             contentInsets: contentInsets,
             dragModeActive: dragModeActive,
             draggingItemID: draggingItemID,
+            appDragModeActive: appDragModeActive,
+            appDropTargetID: appDropTargetID,
             onActivate: onActivate,
             onHoverChange: onHoverChange,
             canReorder: canReorder,
+            canAcceptAppDrop: canAcceptAppDrop,
             onReorderBegan: onReorderBegan,
             onReorderMoved: onReorderMoved,
-            onReorderEnded: onReorderEnded
+            onReorderEnded: onReorderEnded,
+            onAppDropHoverChange: onAppDropHoverChange,
+            onAppDrop: onAppDrop
         )
     }
 }
@@ -82,12 +97,17 @@
         contentInsets: NSEdgeInsets,
         dragModeActive: Bool,
         draggingItemID: String?,
+        appDragModeActive: Bool,
+        appDropTargetID: String?,
         onActivate: @escaping (String) -> Void,
         onHoverChange: @escaping (String, Bool) -> Void,
         canReorder: @escaping (String) -> Bool,
+        canAcceptAppDrop: @escaping (String) -> Bool,
         onReorderBegan: @escaping (String) -> Void,
         onReorderMoved: @escaping (String, String) -> Void,
-        onReorderEnded: @escaping () -> Void
+        onReorderEnded: @escaping () -> Void,
+        onAppDropHoverChange: @escaping (String, Bool) -> Void,
+        onAppDrop: @escaping (String, String, String, Bool) -> Void
     ) {
         documentView.update(
             items: items,
@@ -95,15 +115,20 @@
             contentInsets: contentInsets,
             dragModeActive: dragModeActive,
             draggingItemID: draggingItemID,
+            appDragModeActive: appDragModeActive,
+            appDropTargetID: appDropTargetID,
             onActivate: onActivate,
             onHoverChange: onHoverChange,
             canReorder: canReorder,
+            canAcceptAppDrop: canAcceptAppDrop,
             onReorderBegan: onReorderBegan,
             onReorderMoved: onReorderMoved,
-            onReorderEnded: onReorderEnded
+            onReorderEnded: onReorderEnded,
+            onAppDropHoverChange: onAppDropHoverChange,
+            onAppDrop: onAppDrop
         )
-        scrollView.hasHorizontalScroller = orientation == .horizontal
-        scrollView.hasVerticalScroller = orientation == .vertical
+        scrollView.hasHorizontalScroller = false
+        scrollView.hasVerticalScroller = false
         needsLayout = true
     }
 
@@ -126,6 +151,8 @@
         scrollView.borderType = .noBorder
         scrollView.autohidesScrollers = true
         scrollView.scrollerStyle = .overlay
+        scrollView.horizontalScrollElasticity = .allowed
+        scrollView.verticalScrollElasticity = .allowed
         scrollView.hasHorizontalScroller = false
         scrollView.hasVerticalScroller = false
         scrollView.documentView = documentView
@@ -140,12 +167,17 @@
     private var buttons: [TagNavigationButton] = []
     private var dragModeActive = false
     private var draggingItemID: String?
+    private var appDragModeActive = false
+    private var appDropTargetID: String?
     private var onActivate: (String) -> Void = { _ in }
     private var onHoverChange: (String, Bool) -> Void = { _, _ in }
     private var canReorder: (String) -> Bool = { _ in false }
+    private var canAcceptAppDrop: (String) -> Bool = { _ in false }
     private var onReorderBegan: (String) -> Void = { _ in }
     private var onReorderMoved: (String, String) -> Void = { _, _ in }
     private var onReorderEnded: () -> Void = {}
+    private var onAppDropHoverChange: (String, Bool) -> Void = { _, _ in }
+    private var onAppDrop: (String, String, String, Bool) -> Void = { _, _, _, _ in }
 
     override var isFlipped: Bool { true }
 
@@ -155,12 +187,17 @@
         contentInsets: NSEdgeInsets,
         dragModeActive: Bool,
         draggingItemID: String?,
+        appDragModeActive: Bool,
+        appDropTargetID: String?,
         onActivate: @escaping (String) -> Void,
         onHoverChange: @escaping (String, Bool) -> Void,
         canReorder: @escaping (String) -> Bool,
+        canAcceptAppDrop: @escaping (String) -> Bool,
         onReorderBegan: @escaping (String) -> Void,
         onReorderMoved: @escaping (String, String) -> Void,
-        onReorderEnded: @escaping () -> Void
+        onReorderEnded: @escaping () -> Void,
+        onAppDropHoverChange: @escaping (String, Bool) -> Void,
+        onAppDrop: @escaping (String, String, String, Bool) -> Void
     ) {
         let oldNames = self.items.map(\.name)
         let newNames = items.map(\.name)
@@ -170,12 +207,17 @@
         self.contentInsets = contentInsets
         self.dragModeActive = dragModeActive
         self.draggingItemID = draggingItemID
+        self.appDragModeActive = appDragModeActive
+        self.appDropTargetID = appDropTargetID
         self.onActivate = onActivate
         self.onHoverChange = onHoverChange
         self.canReorder = canReorder
+        self.canAcceptAppDrop = canAcceptAppDrop
         self.onReorderBegan = onReorderBegan
         self.onReorderMoved = onReorderMoved
         self.onReorderEnded = onReorderEnded
+        self.onAppDropHoverChange = onAppDropHoverChange
+        self.onAppDrop = onAppDrop
 
         if needsRebuild {
             rebuildButtons()
@@ -226,6 +268,14 @@
                 self?.handleReorderMove(tagID: tagID, screenPoint: screenPoint)
             }
             button.onReorderEnded = { [weak self] in self?.onReorderEnded() }
+            button.canAcceptAppDrop = { [weak self] tagID in self?.canAcceptAppDrop(tagID) ?? false }
+            button.onAppDropHoverChange = { [weak self] tagID, active in
+                self?.setLocalAppDropHover(tagID, active: active)
+                self?.onAppDropHoverChange(tagID, active)
+            }
+            button.onAppDrop = { [weak self] path, source, targetTag, copy in
+                self?.onAppDrop(path, source, targetTag, copy)
+            }
             addSubview(button)
             return button
         }
@@ -236,17 +286,30 @@
         buttons = items.compactMap { item in
             guard let button = existing[item.name] else { return nil }
             button.configure(item: item, orientation: orientation)
+            button.refreshAppDropRegistration()
             return button
         }
     }
 
     private func updateButtonRuntimeState() {
         for button in buttons {
+            let isAppDropTarget = appDropTargetID == button.itemName
+            let appDropVisualActive = appDropTargetID != nil
             button.configureRuntime(
-                dragModeActive: dragModeActive && canReorder(button.itemName),
-                isDragging: draggingItemID == button.itemName
+                dragModeActive: (dragModeActive && canReorder(button.itemName)) || appDropVisualActive,
+                isDragging: draggingItemID == button.itemName || isAppDropTarget
             )
+            button.refreshAppDropRegistration()
         }
+    }
+
+    private func setLocalAppDropHover(_ tagID: String, active: Bool) {
+        if active {
+            appDropTargetID = tagID
+        } else if appDropTargetID == tagID {
+            appDropTargetID = nil
+        }
+        updateButtonRuntimeState()
     }
 
     private func handleReorderMove(tagID: String, screenPoint: NSPoint) {
@@ -283,13 +346,16 @@
     }
 }
 
-final class TagNavigationButton: NSButton {
+final class TagNavigationButton: NSButton, AppDropTargetReceivingView {
     var onActivate: (String) -> Void = { _ in }
     var onHoverChange: (String, Bool) -> Void = { _, _ in }
     var canReorder: (String) -> Bool = { _ in false }
     var onReorderBegan: (String) -> Void = { _ in }
     var onReorderMoved: (String, NSPoint) -> Void = { _, _ in }
     var onReorderEnded: () -> Void = {}
+    var canAcceptAppDrop: (String) -> Bool = { _ in false }
+    var onAppDropHoverChange: (String, Bool) -> Void = { _, _ in }
+    var onAppDrop: (String, String, String, Bool) -> Void = { _, _, _, _ in }
 
     private var item: TagNavigationItem
     private var orientation: TagNavigationView.Orientation
@@ -297,6 +363,8 @@
     private var isMouseInside = false
     private var dragModeActive = false
     private var isDragging = false
+    private let appDropTargetID = UUID()
+    private var appDropRegistered = false
 
     var itemName: String { item.name }
 
@@ -330,12 +398,16 @@
     }
 
     func configure(item: TagNavigationItem, orientation: TagNavigationView.Orientation) {
+        if self.item.name != item.name {
+            unregisterAppDropTarget()
+        }
         self.item = item
         self.orientation = orientation
         title = item.name
         alignment = orientation == .horizontal ? .center : .left
         setAccessibilityLabel(item.name)
         updateAppearance()
+        refreshAppDropRegistration()
         needsLayout = true
     }
 
@@ -343,6 +415,34 @@
         self.dragModeActive = dragModeActive
         self.isDragging = isDragging
         updateAppearance()
+    }
+
+    func refreshAppDropRegistration() {
+        guard window != nil, canAcceptAppDrop(item.id) else {
+            unregisterAppDropTarget()
+            return
+        }
+        AppDragCoordinator.shared.register(id: appDropTargetID, view: self, tag: item.name)
+        appDropRegistered = true
+    }
+
+    override func viewDidMoveToWindow() {
+        super.viewDidMoveToWindow()
+        refreshAppDropRegistration()
+    }
+
+    func appDragHoverChanged(active: Bool) {
+        guard canAcceptAppDrop(item.id) || !active else { return }
+        onAppDropHoverChange(item.name, active)
+    }
+
+    func performDrop(path: String, source: String, copy: Bool) {
+        guard canAcceptAppDrop(item.id) else { return }
+        onAppDrop(path, source, item.name, copy)
+    }
+
+    deinit {
+        unregisterAppDropTarget()
     }
 
     override func updateTrackingAreas() {
@@ -421,6 +521,12 @@
         onActivate(item.id)
     }
 
+    private func unregisterAppDropTarget() {
+        guard appDropRegistered else { return }
+        AppDragCoordinator.shared.unregister(id: appDropTargetID)
+        appDropRegistered = false
+    }
+
     private func updateAppearance() {
         let background = TagColor.nsColor(for: item.colorIndex)
         let dragScale: CGFloat = isDragging ? 1.04 : 1.0

--
Gitblit v1.9.3