From c1d6cfc2b59b2ce4f1ebf235397cdf43a4292996 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Wed, 24 Jun 2026 20:41:45 +0800
Subject: [PATCH] Release 8.0.1 dark glass themes

---
 src/Apptag/AppGridCollectionView.swift |  193 ++++++++++++++++++++++++++++++++++++------------
 1 files changed, 145 insertions(+), 48 deletions(-)

diff --git a/src/Apptag/AppGridCollectionView.swift b/src/Apptag/AppGridCollectionView.swift
index e5df3a5..5d8d845 100644
--- a/src/Apptag/AppGridCollectionView.swift
+++ b/src/Apptag/AppGridCollectionView.swift
@@ -38,8 +38,8 @@
 }
 
 enum AppGridUsageTipsMetrics {
-    static let barHeight: CGFloat = 84
-    static let reservedHeight: CGFloat = 128
+    static let barHeight: CGFloat = 136
+    static let reservedHeight: CGFloat = 176
     static let bottomMargin: CGFloat = 20
     static let horizontalInset: CGFloat = 24
     static let minWidth: CGFloat = 640
@@ -51,6 +51,7 @@
     let displayMode: String
     let iconSize: CGFloat
     let showNames: Bool
+    let appGridTheme: AppGridTheme
     let bubbleDisabled: Bool
     let showUncommonAppBubbles: Bool
     let highlightedGroupName: String?
@@ -90,6 +91,7 @@
             displayMode: displayMode,
             iconSize: iconSize,
             showNames: showNames,
+            appGridTheme: appGridTheme,
             bubbleDisabled: bubbleDisabled,
             showUncommonAppBubbles: showUncommonAppBubbles,
             highlightedGroupName: highlightedGroupName,
@@ -121,6 +123,7 @@
         var displayMode = AppDefaults.displayMode
         var iconSize: CGFloat = AppDefaults.iconSize
         var showNames = true
+        var appGridTheme = AppGridTheme.fallback
         private var externalBubbleDisabled = false
         private var scrollBubbleDisabled = false
         var showUncommonAppBubbles = AppDefaults.showUncommonAppBubbles
@@ -161,6 +164,7 @@
             displayMode: String,
             iconSize: CGFloat,
             showNames: Bool,
+            appGridTheme: AppGridTheme,
             bubbleDisabled: Bool,
             showUncommonAppBubbles: Bool,
             highlightedGroupName: String?,
@@ -188,6 +192,7 @@
             self.displayMode = displayMode
             self.iconSize = iconSize
             self.showNames = showNames
+            self.appGridTheme = appGridTheme
             self.externalBubbleDisabled = bubbleDisabled
             self.showUncommonAppBubbles = showUncommonAppBubbles
             self.highlightedGroupName = highlightedGroupName
@@ -218,6 +223,7 @@
                 displayMode: displayMode,
                 iconSize: iconSize,
                 showNames: showNames,
+                appGridTheme: appGridTheme,
                 showUncommonAppBubbles: showUncommonAppBubbles,
                 bottomContentPadding: self.bottomContentPadding,
                 contentRevision: contentRevision
@@ -393,6 +399,7 @@
             displayMode: String,
             iconSize: CGFloat,
             showNames: Bool,
+            appGridTheme: AppGridTheme,
             showUncommonAppBubbles: Bool,
             bottomContentPadding: CGFloat,
             contentRevision: Int
@@ -405,6 +412,7 @@
                 displayMode,
                 "\(Int(iconSize.rounded()))",
                 showNames ? "names" : "nonames",
+                "theme=\(appGridTheme.rawValue)",
                 showUncommonAppBubbles ? "uncommon" : "allbubbles",
                 "bottom=\(Int(bottomContentPadding.rounded()))",
                 colorPart,
@@ -477,6 +485,7 @@
 
     func applyCoordinatorUpdate() {
         guard let coordinator else { return }
+        applyAppearance()
         if coordinator.needsReload {
             coordinator.needsReload = false
             collectionView.reloadData()
@@ -766,6 +775,12 @@
         }
         return didScroll
     }
+
+    private func applyAppearance() {
+        let usesDarkGlass = coordinator?.appGridTheme.usesDarkGlass == true
+        layer?.backgroundColor = NSColor.clear.cgColor
+        collectionView.appearance = usesDarkGlass ? NSAppearance(named: .darkAqua) : nil
+    }
 }
 
 private final class AppGridUsageTipsShieldView: NSView {
@@ -943,44 +958,41 @@
         let paddingRight: CGFloat = 16
         let iconSize: CGFloat = 24
         let iconTextGap: CGFloat = 10
-        let titleDetailGap: CGFloat = 22
-        let detailControlsGap: CGFloat = 24
+        let controlZoneWidth: CGFloat = 176
         let buttonGap: CGFloat = 6
         let buttonSize: CGFloat = 48
+        let buttonDotsGap: CGFloat = 6
+        let dotsHeight: CGFloat = 10
         let dotsWidth = dotsView.preferredWidth
-        let fullTitleWidth = ceil(titleLabel.attributedStringValue.size().width)
 
-        let centerY = visualFrame.midY
+        let titleX = visualFrame.minX + paddingLeft + iconSize + iconTextGap
+        let buttonsWidth = buttonSize * 2 + buttonGap
+        let buttonsGroupMinX = visualFrame.maxX - paddingRight - buttonsWidth
+        let textRight = visualFrame.maxX - controlZoneWidth
+        let availableTextWidth = max(1, textRight - titleX)
+        let titleWidth = availableTextWidth
+        let titleHeight: CGFloat = 32
+        let detailHeight: CGFloat = 68
+        let lineGap: CGFloat = 8
+        let textBlockHeight = titleHeight + lineGap + detailHeight
+        let textBlockY = visualFrame.minY + max(0, (visualFrame.height - textBlockHeight) / 2)
+
         iconView.frame = NSRect(
             x: visualFrame.minX + paddingLeft,
-            y: centerY - iconSize / 2,
+            y: visualFrame.midY - iconSize / 2,
             width: iconSize,
             height: iconSize
         )
 
-        let titleX = iconView.frame.maxX + iconTextGap
-        let buttonsWidth = buttonSize * 2 + buttonGap
-        let trailingWidth = paddingRight + buttonsWidth + detailControlsGap
-        let availableTextWidth = max(1, visualFrame.maxX - titleX - titleDetailGap - trailingWidth)
-        let minimumReadableDetailWidth: CGFloat = 220
-        let titleWidth: CGFloat
-        if availableTextWidth >= fullTitleWidth + minimumReadableDetailWidth {
-            titleWidth = fullTitleWidth
-        } else if availableTextWidth >= fullTitleWidth {
-            titleWidth = fullTitleWidth
-        } else {
-            titleWidth = max(1, availableTextWidth)
-        }
-
         titleLabel.frame = NSRect(
             x: titleX,
-            y: visualFrame.minY,
+            y: textBlockY,
             width: titleWidth,
-            height: visualFrame.height
+            height: titleHeight
         )
 
-        let buttonY = visualFrame.minY + 10
-        let buttonsGroupMinX = visualFrame.maxX - paddingRight - buttonsWidth
+        let controlGroupHeight = buttonSize + buttonDotsGap + dotsHeight
+        let buttonY = visualFrame.minY + max(0, (visualFrame.height - controlGroupHeight) / 2)
         nextButton.frame = NSRect(
             x: buttonsGroupMinX + buttonSize + buttonGap,
             y: buttonY,
@@ -995,18 +1007,18 @@
         )
         dotsView.frame = NSRect(
             x: buttonsGroupMinX + (buttonsWidth - dotsWidth) / 2,
-            y: previousButton.frame.maxY + 6,
+            y: previousButton.frame.maxY + buttonDotsGap,
             width: dotsWidth,
-            height: 10
+            height: dotsHeight
         )
 
-        let detailX = titleLabel.frame.maxX + titleDetailGap
-        let detailRight = previousButton.frame.minX - detailControlsGap
+        let detailX = titleX
+        let detailRight = textRight
         detailScrollView.frame = NSRect(
             x: detailX,
-            y: visualFrame.minY,
+            y: titleLabel.frame.maxY + lineGap,
             width: max(1, detailRight - detailX),
-            height: visualFrame.height
+            height: detailHeight
         )
         layoutDetailLabel()
     }
@@ -1048,10 +1060,10 @@
 
     func preferredWidth(maxAvailableWidth: CGFloat) -> CGFloat {
         let padding: CGFloat = 22 + 16
-        let fixedWidth: CGFloat = 24 + 10 + 22 + 24 + 48 + 6 + 48
+        let fixedWidth: CGFloat = 24 + 10 + 24 + 48 + 6 + 48
         let titleWidth = ceil(titleLabel.attributedStringValue.size().width)
-        let detailWidth = ceil(detailLabel.attributedStringValue.size().width)
-        let contentWidth = padding + fixedWidth + titleWidth + detailWidth
+        let detailWidth = measuredDetailLineWidth()
+        let contentWidth = padding + fixedWidth + max(titleWidth, detailWidth)
         let preferredWidth = max(AppGridUsageTipsMetrics.minWidth, contentWidth)
         return min(maxAvailableWidth, preferredWidth)
     }
@@ -1081,7 +1093,7 @@
         addSubview(iconView)
 
         configureLabel(titleLabel, font: titleFont, lineBreakMode: .byClipping)
-        configureLabel(detailLabel, font: detailFont, lineBreakMode: .byClipping)
+        configureDetailLabel()
 
         detailScrollView.drawsBackground = false
         detailScrollView.hasVerticalScroller = false
@@ -1111,7 +1123,21 @@
         label.isBordered = false
         label.lineBreakMode = lineBreakMode
         label.maximumNumberOfLines = 1
+        label.alignment = .center
         label.font = font
+    }
+
+    private func configureDetailLabel() {
+        detailLabel.cell = NSTextFieldCell(textCell: "")
+        detailLabel.isEditable = false
+        detailLabel.isSelectable = false
+        detailLabel.drawsBackground = false
+        detailLabel.isBordered = false
+        detailLabel.lineBreakMode = .byWordWrapping
+        detailLabel.maximumNumberOfLines = 2
+        detailLabel.font = detailFont
+        detailLabel.cell?.usesSingleLineMode = false
+        detailLabel.cell?.wraps = true
     }
 
     private func claimInteractionFocus() {
@@ -1180,9 +1206,17 @@
     }
 
     private func layoutDetailLabel() {
-        let textWidth = detailLabel.attributedStringValue.size().width
-        let width = max(detailScrollView.contentView.bounds.width, ceil(textWidth) + 18)
+        let width = max(1, detailScrollView.contentView.bounds.width)
+        detailLabel.preferredMaxLayoutWidth = width
         detailLabel.frame = NSRect(x: 0, y: 0, width: width, height: detailScrollView.bounds.height)
+    }
+
+    private func measuredDetailLineWidth() -> CGFloat {
+        let attributes: [NSAttributedString.Key: Any] = [.font: detailFont]
+        let lineWidths = detailLabel.stringValue
+            .components(separatedBy: .newlines)
+            .map { NSAttributedString(string: $0, attributes: attributes).size().width }
+        return ceil(lineWidths.max() ?? detailLabel.attributedStringValue.size().width)
     }
 
     private func currentVisualFrame() -> NSRect {
@@ -1198,8 +1232,10 @@
 
     private func formattedTipDetail(_ text: String) -> String {
         text
-            .replacingOccurrences(of: " > ", with: " -> ")
-            .replacingOccurrences(of: ">", with: "->")
+            .replacingOccurrences(of: "\\s*(?:-->|->|>|→|>|,)\\s*", with: "\n", options: .regularExpression)
+            .replacingOccurrences(of: "[ \\t]{2,}", with: " ", options: .regularExpression)
+            .replacingOccurrences(of: "\\n{2,}", with: "\n", options: .regularExpression)
+            .trimmingCharacters(in: .whitespacesAndNewlines)
     }
 
     private func updateColors() {
@@ -1397,6 +1433,7 @@
             x += 5 + spacing
         }
     }
+
 }
 
 private final class AppGridContainerCollectionLayout: NSCollectionViewLayout {
@@ -1961,15 +1998,24 @@
         if displayStyle.usesCardSurface {
             let rect = bounds.insetBy(dx: 0.5, dy: 0.5)
             let path = NSBezierPath(roundedRect: rect, xRadius: 14, yRadius: 14)
-            cardSurfaceColor().setFill()
-            path.fill()
-            if coordinator.isColoredContainerMode || isColorlessActive {
-                tagColor.withAlphaComponent(0.30).setFill()
+            if coordinator.appGridTheme.usesDarkGlass {
+                drawDarkCardSurface(
+                    path: path,
+                    tagColor: tagColor,
+                    isActive: isHovered || isNavigationHighlighted,
+                    isTinted: coordinator.isColoredContainerMode || isColorlessActive
+                )
+            } else {
+                cardSurfaceColor().setFill()
                 path.fill()
+                if coordinator.isColoredContainerMode || isColorlessActive {
+                    tagColor.withAlphaComponent(0.30).setFill()
+                    path.fill()
+                }
+                NSColor.labelColor.withAlphaComponent(0.08).setStroke()
+                path.lineWidth = 1
+                path.stroke()
             }
-            NSColor.labelColor.withAlphaComponent(0.08).setStroke()
-            path.lineWidth = 1
-            path.stroke()
         }
 
         drawHeader(title: group.name, displayStyle: displayStyle)
@@ -2189,6 +2235,17 @@
             && (isHovered || isNavigationHighlighted)
         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 active = isHovered || isNavigationHighlighted
+            layer?.shadowColor = active
+                ? tagColor.withAlphaComponent(0.82).cgColor
+                : NSColor.black.withAlphaComponent(0.72).cgColor
+            layer?.shadowOpacity = active ? 0.50 : 0.28
+            layer?.shadowRadius = active ? 24 : 14
+            layer?.shadowOffset = NSSize(width: 0, height: active ? -8 : -4)
+            return
+        }
         layer?.shadowColor = NSColor.black.cgColor
         layer?.shadowOpacity = shouldShadow ? 0.22 : 0
         layer?.shadowRadius = shouldShadow ? 8 : 0
@@ -2224,6 +2281,40 @@
         return NSColor.white.withAlphaComponent(0.62)
     }
 
+    private func drawDarkCardSurface(
+        path: NSBezierPath,
+        tagColor: NSColor,
+        isActive: Bool,
+        isTinted: Bool
+    ) {
+        NSGraphicsContext.saveGraphicsState()
+        if isActive {
+            let glow = NSShadow()
+            glow.shadowColor = tagColor.withAlphaComponent(0.36)
+            glow.shadowBlurRadius = 28
+            glow.shadowOffset = NSSize(width: 0, height: -8)
+            glow.set()
+        }
+        NSColor(calibratedRed: 0.12, green: 0.18, blue: 0.25, alpha: 0.58).setFill()
+        path.fill()
+        NSGraphicsContext.restoreGraphicsState()
+
+        if isTinted {
+            tagColor.withAlphaComponent(isActive ? 0.18 : 0.10).setFill()
+            path.fill()
+        }
+
+        NSColor.white.withAlphaComponent(isActive ? 0.08 : 0.045).setFill()
+        path.fill()
+
+        let strokeColor = isActive
+            ? tagColor.withAlphaComponent(0.72)
+            : NSColor.white.withAlphaComponent(0.18)
+        strokeColor.setStroke()
+        path.lineWidth = isActive ? 1.4 : 1.0
+        path.stroke()
+    }
+
     private func drawHeader(title: String, displayStyle: AppGridCollectionDisplayMode) {
         let horizontalInset = displayStyle.usesCardSurface ? AppGridCollectionMetrics.cardPadding : 0
         let verticalInset = displayStyle.usesCardSurface ? AppGridCollectionMetrics.cardPadding : 0
@@ -2233,9 +2324,13 @@
             width: max(1, bounds.width - horizontalInset * 2),
             height: AppGridCollectionMetrics.headerHeight
         )
+        let isDarkGrid = coordinator?.appGridTheme.usesDarkGlass == true
+        let headerTextColor = isDarkGrid
+            ? NSColor.white.withAlphaComponent(0.74)
+            : NSColor.secondaryLabelColor
         let attributes: [NSAttributedString.Key: Any] = [
             .font: NSFont.systemFont(ofSize: 18, weight: .semibold),
-            .foregroundColor: NSColor.secondaryLabelColor,
+            .foregroundColor: headerTextColor,
             .paragraphStyle: centeredParagraph(lineBreak: .byTruncatingMiddle)
         ]
         let titleSize = title.size(withAttributes: attributes)
@@ -2247,7 +2342,9 @@
             height: headerRect.height - 6
         )
         let lineY = headerRect.midY
-        NSColor.secondaryLabelColor.withAlphaComponent(0.25).setStroke()
+        (isDarkGrid ? NSColor.white : NSColor.secondaryLabelColor)
+            .withAlphaComponent(isDarkGrid ? 0.16 : 0.25)
+            .setStroke()
         let leftLine = NSBezierPath()
         leftLine.move(to: NSPoint(x: headerRect.minX, y: lineY))
         leftLine.line(to: NSPoint(x: max(headerRect.minX, titleRect.minX - 2), y: lineY))

--
Gitblit v1.9.3