From efbfc27e0c813c31b570d72fded40eb91d07f2db Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Thu, 28 May 2026 21:01:19 +0800
Subject: [PATCH] Add AppKit tag navigation reordering

---
 Apptag/AppGridCollectionView.swift |   38 +++++++++++++++++++++++++++++++-------
 1 files changed, 31 insertions(+), 7 deletions(-)

diff --git a/Apptag/AppGridCollectionView.swift b/Apptag/AppGridCollectionView.swift
index 9fdd8af..013e915 100644
--- a/Apptag/AppGridCollectionView.swift
+++ b/Apptag/AppGridCollectionView.swift
@@ -45,6 +45,7 @@
     let onBubbleHover: (AppInfo, CGRect, AppBubbleHoverEvent) -> Void
     let onEditNote: (AppInfo, CGRect) -> Void
     let onDropApp: (String, String, String, Bool) -> Void
+    let onDropOutsideGroup: (String, String, Bool) -> Void
     let onGroupActivate: (String) -> Void
     let onScrollActivity: () -> Void
     let onDragModeChange: (Bool) -> Void
@@ -76,6 +77,7 @@
             onBubbleHover: onBubbleHover,
             onEditNote: onEditNote,
             onDropApp: onDropApp,
+            onDropOutsideGroup: onDropOutsideGroup,
             onGroupActivate: onGroupActivate,
             onScrollActivity: onScrollActivity,
             onDragModeChange: onDragModeChange
@@ -104,6 +106,7 @@
         var onBubbleHover: (AppInfo, CGRect, AppBubbleHoverEvent) -> Void = { _, _, _ in }
         var onEditNote: (AppInfo, CGRect) -> Void = { _, _ in }
         var onDropApp: (String, String, String, Bool) -> Void = { _, _, _, _ in }
+        var onDropOutsideGroup: (String, String, Bool) -> Void = { _, _, _ in }
         var onGroupActivate: (String) -> Void = { _ in }
         var onScrollActivity: () -> Void = {}
         var onDragModeChange: (Bool) -> Void = { _ in }
@@ -124,6 +127,7 @@
             onBubbleHover: @escaping (AppInfo, CGRect, AppBubbleHoverEvent) -> Void,
             onEditNote: @escaping (AppInfo, CGRect) -> Void,
             onDropApp: @escaping (String, String, String, Bool) -> Void,
+            onDropOutsideGroup: @escaping (String, String, Bool) -> Void,
             onGroupActivate: @escaping (String) -> Void,
             onScrollActivity: @escaping () -> Void,
             onDragModeChange: @escaping (Bool) -> Void
@@ -143,6 +147,7 @@
             self.onBubbleHover = onBubbleHover
             self.onEditNote = onEditNote
             self.onDropApp = onDropApp
+            self.onDropOutsideGroup = onDropOutsideGroup
             self.onGroupActivate = onGroupActivate
             self.onScrollActivity = onScrollActivity
             self.onDragModeChange = onDragModeChange
@@ -153,7 +158,6 @@
                 iconSize: iconSize,
                 showNames: showNames,
                 showUncommonAppBubbles: showUncommonAppBubbles,
-                highlightedGroupName: highlightedGroupName,
                 contentRevision: contentRevision
             )
             if nextSignature != contentSignature {
@@ -230,7 +234,6 @@
             iconSize: CGFloat,
             showNames: Bool,
             showUncommonAppBubbles: Bool,
-            highlightedGroupName: String?,
             contentRevision: Int
         ) -> String {
             let colorPart = tagColors
@@ -242,7 +245,6 @@
                 "\(Int(iconSize.rounded()))",
                 showNames ? "names" : "nonames",
                 showUncommonAppBubbles ? "uncommon" : "allbubbles",
-                highlightedGroupName ?? "",
                 colorPart,
                 "rev=\(contentRevision)"
             ].joined(separator: "|")
@@ -250,7 +252,8 @@
     }
 }
 
-final class AppGridCollectionHostView: NSView {
+final class AppGridCollectionHostView: NSView, AppEmptyDropReceivingView {
+    private let emptyDropTargetID = UUID()
     private let scrollView = NSScrollView()
     private let collectionView = NSCollectionView()
     private let gridLayout = AppGridContainerCollectionLayout()
@@ -277,6 +280,7 @@
             NotificationCenter.default.removeObserver(scrollObserver)
         }
         scrollUnfreezeWorkItem?.cancel()
+        AppDragCoordinator.shared.unregisterEmptyDropTarget(id: emptyDropTargetID)
     }
 
     func configure(coordinator: AppGridCollectionView.Coordinator) {
@@ -316,6 +320,24 @@
         if lastLayoutSize != bounds.size {
             lastLayoutSize = bounds.size
             gridLayout.invalidateLayout()
+        }
+    }
+
+    override func viewDidMoveToWindow() {
+        super.viewDidMoveToWindow()
+        if window == nil {
+            AppDragCoordinator.shared.unregisterEmptyDropTarget(id: emptyDropTargetID)
+        } else {
+            AppDragCoordinator.shared.registerEmptyDropTarget(id: emptyDropTargetID, view: self)
+        }
+    }
+
+    func performEmptyDrop(path: String, source: String, screenPoint: NSPoint, copy: Bool) {
+        guard let coordinator,
+              coordinator.displayStyle != .flat
+        else { return }
+        DispatchQueue.main.async {
+            coordinator.onDropOutsideGroup(path, source, copy)
         }
     }
 
@@ -926,8 +948,9 @@
         guard let coordinator, let group else { return }
         let displayStyle = coordinator.displayStyle
         let tagColor = TagColor.nsColor(for: coordinator.tagColors[group.name] ?? 0)
+        let isNavigationHighlighted = coordinator.highlightedGroupName == group.name
         let isColorlessActive = coordinator.isColorlessContainerMode
-            && (isHovered || coordinator.highlightedGroupName == group.name)
+            && (isHovered || isNavigationHighlighted)
 
         if displayStyle.usesCardSurface {
             let rect = bounds.insetBy(dx: 0.5, dy: 0.5)
@@ -1008,10 +1031,11 @@
 
     private func updateCardShadow() {
         guard let coordinator else { return }
+        let isNavigationHighlighted = coordinator.highlightedGroupName == group?.name
         let isColorlessActive = coordinator.isColorlessContainerMode
-            && (isHovered || coordinator.highlightedGroupName == group?.name)
+            && (isHovered || isNavigationHighlighted)
         let shouldShadow = coordinator.displayStyle.usesCardSurface
-            && ((coordinator.isColoredContainerMode && isHovered) || isColorlessActive)
+            && ((coordinator.isColoredContainerMode && (isHovered || isNavigationHighlighted)) || isColorlessActive)
         layer?.shadowColor = NSColor.black.cgColor
         layer?.shadowOpacity = shouldShadow ? 0.22 : 0
         layer?.shadowRadius = shouldShadow ? 8 : 0

--
Gitblit v1.9.3