From 0f7ee6e29f70017dd4fdfe2dd3a6106f7204f04f Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Thu, 02 Jul 2026 02:40:40 +0800
Subject: [PATCH] Document Raycast extension backlog

---
 src/Apptag/AppGridCollectionView.swift |   79 +++++++++++++++++++++++++++++++++++++--
 1 files changed, 74 insertions(+), 5 deletions(-)

diff --git a/src/Apptag/AppGridCollectionView.swift b/src/Apptag/AppGridCollectionView.swift
index d124a61..ba2b864 100644
--- a/src/Apptag/AppGridCollectionView.swift
+++ b/src/Apptag/AppGridCollectionView.swift
@@ -48,6 +48,7 @@
 struct AppGridCollectionView: NSViewRepresentable {
     let groups: [TagGroup]
     let tagColors: [String: Int]
+    let tagCustomColors: [String: TagCustomColor]
     let displayMode: String
     let iconSize: CGFloat
     let showNames: Bool
@@ -89,6 +90,7 @@
         context.coordinator.update(
             groups: groups,
             tagColors: tagColors,
+            tagCustomColors: tagCustomColors,
             displayMode: displayMode,
             iconSize: iconSize,
             showNames: showNames,
@@ -122,6 +124,7 @@
     final class Coordinator: NSObject, NSCollectionViewDataSource, NSCollectionViewDelegate {
         var groups: [TagGroup] = []
         var tagColors: [String: Int] = [:]
+        var tagCustomColors: [String: TagCustomColor] = [:]
         var displayMode = AppDefaults.displayMode
         var iconSize: CGFloat = AppDefaults.iconSize
         var showNames = true
@@ -164,6 +167,7 @@
         func update(
             groups: [TagGroup],
             tagColors: [String: Int],
+            tagCustomColors: [String: TagCustomColor],
             displayMode: String,
             iconSize: CGFloat,
             showNames: Bool,
@@ -193,6 +197,7 @@
         ) {
             self.groups = groups
             self.tagColors = tagColors
+            self.tagCustomColors = tagCustomColors
             self.displayMode = displayMode
             self.iconSize = iconSize
             self.showNames = showNames
@@ -225,6 +230,7 @@
 
             let nextSignature = Self.signature(
                 tagColors: tagColors,
+                tagCustomColors: tagCustomColors,
                 displayMode: displayMode,
                 iconSize: iconSize,
                 showNames: showNames,
@@ -401,6 +407,7 @@
 
         private static func signature(
             tagColors: [String: Int],
+            tagCustomColors: [String: TagCustomColor],
             displayMode: String,
             iconSize: CGFloat,
             showNames: Bool,
@@ -413,6 +420,10 @@
                 .sorted { $0.key < $1.key }
                 .map { "\($0.key)=\($0.value)" }
                 .joined(separator: ",")
+            let customColorPart = tagCustomColors
+                .sorted { $0.key < $1.key }
+                .map { "\($0.key)=\($0.value.signature)" }
+                .joined(separator: ",")
             return [
                 displayMode,
                 "\(Int(iconSize.rounded()))",
@@ -421,8 +432,16 @@
                 showUncommonAppBubbles ? "uncommon" : "allbubbles",
                 "bottom=\(Int(bottomContentPadding.rounded()))",
                 colorPart,
+                customColorPart,
                 "rev=\(contentRevision)"
             ].joined(separator: "|")
+        }
+
+        func tagColor(for groupName: String) -> NSColor {
+            TagColor.nsColor(
+                for: tagColors[groupName] ?? 0,
+                customColor: tagCustomColors[groupName]
+            )
         }
 
         private static func clampedUsageTipIndex(_ index: Int, tips: [AppGridUsageTip]) -> Int {
@@ -1777,7 +1796,7 @@
 
     private struct LayoutItem {
         let index: Int
-        let frame: NSRect
+        var frame: NSRect
     }
 
     private struct LayoutPlan {
@@ -1913,8 +1932,20 @@
         let columnWidth = floor((availableWidth - gap * CGFloat(columnCount - 1)) / CGFloat(columnCount))
         var columnHeights = Array(repeating: outerPadding, count: columnCount)
         var items: [LayoutItem] = []
+        var lastItemIndexForColumn = Array<Int?>(repeating: nil, count: columnCount)
+        let bottomPinnedGroups = groups.enumerated()
+            .filter { bottomPinnedPriority(for: $0.element.containerID) != nil }
+            .sorted {
+                let lhsPriority = bottomPinnedPriority(for: $0.element.containerID) ?? Int.max
+                let rhsPriority = bottomPinnedPriority(for: $1.element.containerID) ?? Int.max
+                if lhsPriority != rhsPriority { return lhsPriority < rhsPriority }
+                return $0.offset < $1.offset
+            }
+        let bottomPinnedIndexes = Set(bottomPinnedGroups.map { $0.offset })
 
         for (index, group) in groups.enumerated() {
+            if bottomPinnedIndexes.contains(index) { continue }
+
             let columnIndex = columnHeights.indices.min { columnHeights[$0] < columnHeights[$1] } ?? 0
             let x = outerPadding + CGFloat(columnIndex) * (columnWidth + gap)
             let y = columnHeights[columnIndex]
@@ -1929,16 +1960,54 @@
                     frame: NSRect(x: x, y: y, width: columnWidth, height: height)
                 )
             )
+            lastItemIndexForColumn[columnIndex] = items.count - 1
             columnHeights[columnIndex] = y + height + gap
         }
 
-        let tallest = columnHeights.max() ?? outerPadding
-        let contentHeight = (groups.isEmpty ? outerPadding * 2 : tallest - gap + outerPadding)
+        var contentBottom = columnHeights.max() ?? outerPadding
+        if !bottomPinnedGroups.isEmpty {
+            let alignedBottom = max(outerPadding, contentBottom - gap)
+            for itemIndex in lastItemIndexForColumn.compactMap({ $0 }) {
+                let currentFrame = items[itemIndex].frame
+                let alignedHeight = alignedBottom - currentFrame.minY
+                if alignedHeight > currentFrame.height {
+                    items[itemIndex].frame.size.height = alignedHeight
+                }
+            }
+        }
+        for (index, group) in bottomPinnedGroups {
+            let y = max(outerPadding, contentBottom)
+            let height = AppGridCollectionMetrics.cardHeight(
+                appCount: group.apps.count,
+                width: availableWidth,
+                iconSize: iconSize
+            )
+            items.append(
+                LayoutItem(
+                    index: index,
+                    frame: NSRect(x: outerPadding, y: y, width: availableWidth, height: height)
+                )
+            )
+            contentBottom = y + height + gap
+        }
+
+        let contentHeight = (items.isEmpty ? outerPadding * 2 : contentBottom - gap + outerPadding)
             + max(0, bottomContentPadding)
         return LayoutPlan(
             items: items,
             contentSize: NSSize(width: boundedContentWidth, height: max(1, contentHeight))
         )
+    }
+
+    private static func bottomPinnedPriority(for containerID: String) -> Int? {
+        switch containerID {
+        case AppContainerID.uncategorized:
+            return 0
+        case AppContainerID.appleBuiltIn:
+            return 1
+        default:
+            return nil
+        }
     }
 
     private struct RowItem {
@@ -2275,7 +2344,7 @@
     override func draw(_ dirtyRect: NSRect) {
         guard let coordinator, let group else { return }
         let displayStyle = coordinator.displayStyle
-        let tagColor = TagColor.nsColor(for: coordinator.tagColors[group.name] ?? 0)
+        let tagColor = coordinator.tagColor(for: group.name)
         let isNavigationHighlighted = coordinator.highlightedGroupName == group.name
         let isColorlessActive = coordinator.isColorlessContainerMode
             && (isHovered || isNavigationHighlighted)
@@ -2521,7 +2590,7 @@
         let shouldShadow = coordinator.displayStyle.usesCardSurface
             && ((coordinator.isColoredContainerMode && (isHovered || isNavigationHighlighted)) || isColorlessActive)
         if coordinator.appGridTheme.usesDarkGlass && coordinator.displayStyle.usesCardSurface {
-            let tagColor = TagColor.nsColor(for: coordinator.tagColors[group?.name ?? ""] ?? 0)
+            let tagColor = coordinator.tagColor(for: group?.name ?? "")
             let active = isHovered || isNavigationHighlighted
             layer?.shadowColor = active
                 ? tagColor.withAlphaComponent(0.82).cgColor

--
Gitblit v1.9.3