From f6c42d1a709e55de9895c564401165ca6c2e3f8e Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Fri, 29 May 2026 01:06:51 +0800
Subject: [PATCH] Rename app grid shared support file

---
 Apptag/ContentView.swift |  151 +++++++++++++++++++++++++++++++++++++++-----------
 1 files changed, 117 insertions(+), 34 deletions(-)

diff --git a/Apptag/ContentView.swift b/Apptag/ContentView.swift
index f94de57..da98d57 100644
--- a/Apptag/ContentView.swift
+++ b/Apptag/ContentView.swift
@@ -316,6 +316,9 @@
     var tagRemovalDropSuppressFuturePrompt = false
     var appDragResetToken = 0
     var bubbleDraftNote = ""
+    var tagNavigationHoveredGroupName: String? = nil
+    var tagNavigationLastHoverScrollID: String? = nil
+    var tagNavigationLastHoverScrollAt: Date? = nil
 }
 
 private struct EditActionFeedback: Identifiable {
@@ -418,6 +421,7 @@
     @AppStorage("displayMode") private var displayMode = AppDefaults.displayMode
     @AppStorage("hideAppNames") private var hideAppNames = AppDefaults.hideAppNames
     @AppStorage("showUncommonAppBubbles") private var showUncommonAppBubbles = AppDefaults.showUncommonAppBubbles
+    @AppStorage("useAppKitTagNavigation") private var useAppKitTagNavigation = AppDefaults.useAppKitTagNavigation
     @AppStorage("skipTagRemovalDropConfirm") private var skipTagRemovalDropConfirm = false
 
     private let editSidebarWidth: CGFloat = 188
@@ -429,7 +433,12 @@
             || appGridInteraction.pendingUncategorizedDrop != nil
             || appGridInteraction.pendingTagRemovalDrop != nil
     }
+    private var appGridHighlightedGroupName: String? {
+        appGridInteraction.tagNavigationHoveredGroupName
+            ?? (isColorlessContainerMode ? filledColorlessContainer : nil)
+    }
     private let rightSidebarFloatingClearance: CGFloat = 44
+    private let tagNavigationHoverScrollInterval: TimeInterval = 0.22
 
     private var floatingButtonSurfaceColor: Color {
         colorScheme == .dark
@@ -865,7 +874,7 @@
     private var topLayout: some View {
         VStack(spacing: 0) {
             Spacer().frame(height: notchHeight > 0 ? notchHeight + 14 : 28)
-            if !tagLabels.isEmpty { tagBar.padding(.bottom, 8) }
+            if !tagLabels.isEmpty { topTagNavigation.padding(.bottom, 8) }
             Divider().opacity(0.3)
             appGridContent
         }
@@ -876,14 +885,79 @@
             Spacer().frame(height: notchHeight > 0 ? notchHeight + 14 : 28)
             Divider().opacity(0.3)
             HStack(spacing: 0) {
-                if tagPosition == "left" { tagSidebar; sideDivider }
+                if tagPosition == "left" { leftTagSidebar; sideDivider }
                 appGridContent
                 if tagPosition == "right" { sideDivider; rightTagSidebar }
             }
         }
     }
 
-    private var tagBar: some View {
+    private var topTagNavigation: some View {
+        Group {
+            if useAppKitTagNavigation {
+                appKitTagNavigation(orientation: .horizontal)
+                    .frame(height: 34)
+            } else {
+                swiftUITopTagBar
+            }
+        }
+    }
+
+    private var leftTagSidebar: some View {
+        Group {
+            if useAppKitTagNavigation {
+                appKitTagNavigation(orientation: .vertical)
+            } else {
+                swiftUITagSidebarList
+            }
+        }
+        .frame(width: 135)
+    }
+
+    private var rightTagSidebar: some View {
+        Group {
+            if useAppKitTagNavigation {
+                appKitTagNavigation(orientation: .vertical)
+            } else {
+                swiftUITagSidebarList
+            }
+        }
+        .padding(.top, rightSidebarFloatingClearance)
+        .frame(width: 135)
+    }
+
+    private func appKitTagNavigation(orientation: TagNavigationView.Orientation) -> some View {
+        let isHorizontal = orientation == .horizontal
+        return TagNavigationView(
+            items: tagLabels,
+            orientation: orientation,
+            contentInsets: isHorizontal
+                ? NSEdgeInsets(top: 3, left: 24, bottom: 3, right: tagPosition == "top" ? floatingControlsReservedWidth : 24)
+                : NSEdgeInsets(top: 12, left: 12, bottom: 12, right: 12),
+            dragModeActive: tagNavDragModeActive,
+            draggingItemID: tagNavDragItem,
+            onActivate: { tagID in
+                activateTagNavigation(tagID)
+            },
+            onHoverChange: { tagID, active in
+                handleTagNavigationHover(tagID, active: active)
+            },
+            canReorder: { tagID in
+                canReorderTag(tagID)
+            },
+            onReorderBegan: { tagID in
+                beginTagNavReorder(tagID)
+            },
+            onReorderMoved: { tagID, targetID in
+                reorderTagNavItem(fromName: tagID, to: targetID)
+            },
+            onReorderEnded: {
+                endTagNavReorder()
+            }
+        )
+    }
+
+    private var swiftUITopTagBar: some View {
         ScrollView(.horizontal, showsIndicators: false) {
             HStack(spacing: 8) {
                 ForEach(tagLabels) { tag in
@@ -897,10 +971,7 @@
                     .zIndex(tagNavDragItem == tag.name ? 1 : 0)
                     .highPriorityGesture(tagNavReorderGesture(for: tag.name))
                     .onHover { hovering in
-                        if hovering {
-                            fillColorlessContainer(tag.id)
-                            scrollTo(tag.id)
-                        }
+                        handleTagNavigationHover(tag.id, active: hovering)
                     }
                 }
             }
@@ -913,18 +984,7 @@
         }
     }
 
-    private var tagSidebar: some View {
-        tagSidebarList
-            .frame(width: 135)
-    }
-
-    private var rightTagSidebar: some View {
-        tagSidebarList
-            .padding(.top, rightSidebarFloatingClearance)
-            .frame(width: 135)
-    }
-
-    private var tagSidebarList: some View {
+    private var swiftUITagSidebarList: some View {
         ScrollView(.vertical, showsIndicators: false) {
             VStack(spacing: 6) {
                 ForEach(tagLabels) { tag in
@@ -938,10 +998,7 @@
                     .zIndex(tagNavDragItem == tag.name ? 1 : 0)
                     .highPriorityGesture(tagNavReorderGesture(for: tag.name))
                     .onHover { hovering in
-                        if hovering {
-                            fillColorlessContainer(tag.id)
-                            scrollTo(tag.id)
-                        }
+                        handleTagNavigationHover(tag.id, active: hovering)
                     }
                 }
             }
@@ -972,7 +1029,7 @@
                     showNames: !hideAppNames,
                     bubbleDisabled: appBubbleDisabled,
                     showUncommonAppBubbles: showUncommonAppBubbles,
-                    highlightedGroupName: filledColorlessContainer,
+                    highlightedGroupName: appGridHighlightedGroupName,
                     contentRevision: groupLayoutVersion,
                     scrollTargetID: appGridScrollTargetID,
                     scrollRequestToken: appGridScrollRequestToken,
@@ -1732,8 +1789,8 @@
         groupLayoutVersion &+= 1
     }
 
-    private var tagLabels: [TagLabel] {
-        displayGroups.map { TagLabel(name: $0.name, colorIndex: tagColors[$0.name] ?? 0) }
+    private var tagLabels: [TagNavigationItem] {
+        displayGroups.map { TagNavigationItem(name: $0.name, colorIndex: tagColors[$0.name] ?? 0) }
     }
 
     private var editGroups: [TagGroup] {
@@ -1906,6 +1963,31 @@
         scrollTo(id)
     }
 
+    private func handleTagNavigationHover(_ id: String, active: Bool) {
+        guard active else {
+            if appGridInteraction.tagNavigationHoveredGroupName == id {
+                appGridInteraction.tagNavigationHoveredGroupName = nil
+            }
+            return
+        }
+
+        appGridInteraction.tagNavigationHoveredGroupName = id
+        fillColorlessContainer(id)
+        scrollToTagFromHover(id)
+    }
+
+    private func scrollToTagFromHover(_ id: String) {
+        let now = Date()
+        if appGridInteraction.tagNavigationLastHoverScrollID == id,
+           let lastScrollAt = appGridInteraction.tagNavigationLastHoverScrollAt,
+           now.timeIntervalSince(lastScrollAt) < tagNavigationHoverScrollInterval {
+            return
+        }
+        appGridInteraction.tagNavigationLastHoverScrollID = id
+        appGridInteraction.tagNavigationLastHoverScrollAt = now
+        scrollTo(id)
+    }
+
     private func fillColorlessContainer(_ id: String) {
         guard isColorlessContainerMode else { return }
         guard filledColorlessContainer != id else { return }
@@ -1986,11 +2068,18 @@
     private func reorderTagNavItem(at location: CGPoint) {
         guard let fromName = tagNavDragItem,
               let targetName = tagNavReorderFrames.first(where: { $0.value.contains(location) })?.key,
-              fromName != targetName,
+              fromName != targetName
+        else { return }
+        reorderTagNavItem(fromName: fromName, to: targetName)
+    }
+
+    private func reorderTagNavItem(fromName: String, to targetName: String) {
+        guard fromName != targetName,
+              canReorderTag(fromName),
+              canReorderTag(targetName),
               let fromIndex = draggedTagNames.firstIndex(of: fromName),
               let toIndex = draggedTagNames.firstIndex(of: targetName)
         else { return }
-
         tagNavReorderDidMove = true
         withAnimation(.spring(response: 0.22, dampingFraction: 0.82)) {
             let destination = toIndex > fromIndex ? toIndex + 1 : toIndex
@@ -2360,12 +2449,6 @@
     static func reduce(value: inout [String: CGRect], nextValue: () -> [String: CGRect]) {
         value.merge(nextValue(), uniquingKeysWith: { _, new in new })
     }
-}
-
-private struct TagLabel: Identifiable {
-    var id: String { name }
-    let name: String
-    let colorIndex: Int
 }
 
 // MARK: - NSVisualEffectView bridge

--
Gitblit v1.9.3