From 5912f1a8529bbc400b22e7c390a5cc9c09dd8a27 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Sun, 07 Jun 2026 01:14:09 +0800
Subject: [PATCH] Record 7.8.15 freeze manifest

---
 Apptag/QuickSearch.swift |  156 +++++++++++++++++++++++++++++++++++++++------------
 1 files changed, 118 insertions(+), 38 deletions(-)

diff --git a/Apptag/QuickSearch.swift b/Apptag/QuickSearch.swift
index aab4e8f..b933fd5 100644
--- a/Apptag/QuickSearch.swift
+++ b/Apptag/QuickSearch.swift
@@ -15,6 +15,13 @@
     static let globalVisible = "globalVisible"
 }
 
+enum QuickSearchDismissSource {
+    static let keyboard = "keyboard"
+    static let mouse = "mouse"
+    static let backdrop = "backdrop"
+    static let programmatic = "programmatic"
+}
+
 // MARK: - Hotkeys
 
 struct LauncherHotkey: Equatable {
@@ -731,6 +738,7 @@
 
 enum QuickSearchPanelMetrics {
     static let width: CGFloat = 760
+    static let shadowOutset: CGFloat = 56
     static let rowHeight: CGFloat = 74
     static let rowSpacing: CGFloat = 2
     static let resultListVerticalInset: CGFloat = 10
@@ -769,9 +777,14 @@
     }
 
     func updateNSView(_ view: QuickSearchPanelAnchorView, context: Context) {
-        let size = NSSize(width: QuickSearchPanelMetrics.width, height: panelHeight)
+        let contentSize = NSSize(width: QuickSearchPanelMetrics.width, height: panelHeight)
+        let windowSize = NSSize(
+            width: contentSize.width + QuickSearchPanelMetrics.shadowOutset * 2,
+            height: contentSize.height + QuickSearchPanelMetrics.shadowOutset * 2
+        )
         let rootView = QuickSearchPanelWindowContent(
-            size: size,
+            windowSize: windowSize,
+            contentSize: contentSize,
             content: AnyView(
                 QuickSearchOverlayView(
                     query: $query,
@@ -791,7 +804,8 @@
         context.coordinator.apply(
             QuickSearchPanelConfiguration(
                 panelTopY: panelTopY,
-                size: size,
+                windowSize: windowSize,
+                contentSize: contentSize,
                 rootView: AnyView(rootView)
             ),
             anchorView: view
@@ -823,12 +837,13 @@
             let frame = windowFrame(
                 parentWindow: parentWindow,
                 panelTopY: configuration.panelTopY,
-                size: configuration.size
+                contentSize: configuration.contentSize,
+                windowSize: configuration.windowSize
             )
 
             hostingView?.rootView = configuration.rootView
-            hostingView?.frame = NSRect(origin: .zero, size: configuration.size)
-            panel.contentView?.frame = NSRect(origin: .zero, size: configuration.size)
+            hostingView?.frame = NSRect(origin: .zero, size: configuration.windowSize)
+            panel.contentView?.frame = NSRect(origin: .zero, size: configuration.windowSize)
             panel.collectionBehavior = collectionBehavior(parentWindow: parentWindow)
             panel.level = parentWindow.level
 
@@ -907,14 +922,15 @@
         private func windowFrame(
             parentWindow: NSWindow,
             panelTopY: CGFloat,
-            size: NSSize
+            contentSize: NSSize,
+            windowSize: NSSize
         ) -> NSRect {
             let parentFrame = parentWindow.frame
             return NSRect(
-                x: parentFrame.minX + (parentFrame.width - size.width) / 2,
-                y: parentFrame.maxY - panelTopY - size.height,
-                width: size.width,
-                height: size.height
+                x: parentFrame.minX + (parentFrame.width - contentSize.width) / 2 - QuickSearchPanelMetrics.shadowOutset,
+                y: parentFrame.maxY - panelTopY - contentSize.height - QuickSearchPanelMetrics.shadowOutset,
+                width: windowSize.width,
+                height: windowSize.height
             )
         }
 
@@ -961,17 +977,27 @@
 
 struct QuickSearchPanelConfiguration {
     let panelTopY: CGFloat
-    let size: NSSize
+    let windowSize: NSSize
+    let contentSize: NSSize
     let rootView: AnyView
 }
 
 private struct QuickSearchPanelWindowContent: View {
-    let size: NSSize
+    let windowSize: NSSize
+    let contentSize: NSSize
     let content: AnyView
 
     var body: some View {
-        content
-            .frame(width: size.width, height: size.height, alignment: .top)
+        VStack(spacing: 0) {
+            content
+                .frame(width: contentSize.width, height: contentSize.height, alignment: .top)
+            Spacer(minLength: 0)
+        }
+        .padding(.top, QuickSearchPanelMetrics.shadowOutset)
+        .padding(.horizontal, QuickSearchPanelMetrics.shadowOutset)
+        .padding(.bottom, QuickSearchPanelMetrics.shadowOutset)
+        .frame(width: windowSize.width, height: windowSize.height, alignment: .top)
+        .background(Color.clear)
     }
 }
 
@@ -997,6 +1023,10 @@
         colorScheme == .dark
             ? Color(red: 0.105, green: 0.110, blue: 0.125).opacity(0.97)
             : Color.white.opacity(0.97)
+    }
+
+    private var panelShape: RoundedRectangle {
+        RoundedRectangle(cornerRadius: 34, style: .continuous)
     }
 
     var body: some View {
@@ -1061,14 +1091,15 @@
         }
         .frame(width: panelWidth)
         .background(
-            RoundedRectangle(cornerRadius: 34, style: .continuous)
-                .fill(panelBackgroundColor)
-                .shadow(color: .black.opacity(0.18), radius: 36, y: 18)
+            panelShape.fill(panelBackgroundColor)
         )
+        .clipShape(panelShape)
         .overlay(
-            RoundedRectangle(cornerRadius: 34, style: .continuous)
-                .stroke(Color.primary.opacity(0.10), lineWidth: 1)
+            panelShape.stroke(Color.primary.opacity(0.10), lineWidth: 1)
         )
+        .compositingGroup()
+        .shadow(color: .black.opacity(colorScheme == .dark ? 0.46 : 0.26), radius: 30, x: 0, y: 18)
+        .shadow(color: .black.opacity(colorScheme == .dark ? 0.24 : 0.12), radius: 8, x: 0, y: 3)
         .accessibilityElement(children: .contain)
         .accessibilityLabel(tr("quickSearch.title"))
     }
@@ -1273,6 +1304,18 @@
 }
 
 final class QuickSearchResultRowView: NSView {
+    private enum TagBadgeLayout {
+        static let columnWidth: CGFloat = 196
+        static let minWidth: CGFloat = 80
+        static let maxWidth: CGFloat = 196
+        static let height: CGFloat = 32
+        static let horizontalInset: CGFloat = 16
+        static let labelSafetyWidth: CGFloat = 8
+        static let trailingInset: CGFloat = 18
+        static let textGap: CGFloat = 18
+        static let minimumMainTextWidth: CGFloat = 260
+    }
+
     private let iconView = NSImageView()
     private let titleLabel = NSTextField(labelWithString: "")
     private let detailLabel = NSTextField(labelWithString: "")
@@ -1332,25 +1375,44 @@
             height: iconSize
         )
 
-        let tagMaxWidth: CGFloat = 128
-        let tagHeight: CGFloat = 32
-        let trailingInset: CGFloat = 18
+        let textX = iconX + iconSize + 16
+        let trailingInset = TagBadgeLayout.trailingInset
+        let rightLimit = bounds.width - trailingInset
         var tagFrame = NSRect.zero
-        if !tagBackgroundView.isHidden {
-            let labelWidth = min(tagMaxWidth - 24, max(22, tagLabel.intrinsicContentSize.width))
-            let tagWidth = min(tagMaxWidth, labelWidth + 24)
+        let hasTag = !tagLabel.stringValue.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
+        let availableBadgeColumnWidth = rightLimit - textX - TagBadgeLayout.minimumMainTextWidth - TagBadgeLayout.textGap
+        let tagColumnWidth = min(TagBadgeLayout.columnWidth, max(0, availableBadgeColumnWidth))
+        let canShowTag = hasTag && tagColumnWidth >= TagBadgeLayout.minWidth
+        tagBackgroundView.isHidden = !canShowTag
+
+        if canShowTag {
+            let measuredLabelWidth = measuredTagLabelWidth()
+            let measuredBadgeWidth = measuredLabelWidth
+                + TagBadgeLayout.horizontalInset * 2
+                + TagBadgeLayout.labelSafetyWidth
+            let tagWidth = min(
+                min(TagBadgeLayout.maxWidth, tagColumnWidth),
+                max(TagBadgeLayout.minWidth, measuredBadgeWidth)
+            )
             tagFrame = NSRect(
-                x: bounds.width - trailingInset - tagWidth,
-                y: (bounds.height - tagHeight) / 2,
+                x: rightLimit - tagWidth,
+                y: (bounds.height - TagBadgeLayout.height) / 2,
                 width: tagWidth,
-                height: tagHeight
+                height: TagBadgeLayout.height
             )
             tagBackgroundView.frame = tagFrame
-            tagLabel.frame = NSRect(x: 12, y: 6, width: tagWidth - 24, height: 20)
+            tagLabel.frame = NSRect(
+                x: TagBadgeLayout.horizontalInset,
+                y: 6,
+                width: tagWidth - TagBadgeLayout.horizontalInset * 2,
+                height: 20
+            )
+        } else {
+            tagBackgroundView.frame = .zero
+            tagLabel.frame = .zero
         }
 
-        let textX = iconX + iconSize + 16
-        let textRight = tagBackgroundView.isHidden ? bounds.width - trailingInset : tagFrame.minX - 16
+        let textRight = tagBackgroundView.isHidden ? bounds.width - trailingInset : tagFrame.minX - TagBadgeLayout.textGap
         let textWidth = max(40, textRight - textX)
         if detailLabel.isHidden {
             titleLabel.frame = NSRect(x: textX, y: (bounds.height - 26) / 2, width: textWidth, height: 26)
@@ -1359,6 +1421,11 @@
             titleLabel.frame = NSRect(x: textX, y: 14, width: textWidth, height: 25)
             detailLabel.frame = NSRect(x: textX, y: 42, width: textWidth, height: 21)
         }
+    }
+
+    private func measuredTagLabelWidth() -> CGFloat {
+        let font = tagLabel.font ?? NSFont.systemFont(ofSize: 14, weight: .semibold)
+        return ceil((tagLabel.stringValue as NSString).size(withAttributes: [.font: font]).width)
     }
 
     override func updateTrackingAreas() {
@@ -1385,7 +1452,9 @@
 
     override func mouseDown(with event: NSEvent) {
         if let result {
-            onLaunch(result)
+            DispatchQueue.main.async { [onLaunch] in
+                onLaunch(result)
+            }
         } else {
             super.mouseDown(with: event)
         }
@@ -1426,6 +1495,9 @@
         tagLabel.lineBreakMode = .byTruncatingTail
         tagLabel.maximumNumberOfLines = 1
         tagLabel.backgroundColor = .clear
+        tagLabel.allowsDefaultTighteningForTruncation = true
+        tagLabel.cell?.usesSingleLineMode = true
+        tagLabel.cell?.lineBreakMode = .byTruncatingTail
     }
 
     private func updateColors() {
@@ -1524,11 +1596,17 @@
     }
 
     private func requestFocus(_ field: QuickSearchNativeTextField) {
-        DispatchQueue.main.async { [weak field] in
-            guard let field,
-                  field.window?.firstResponder !== field
-            else { return }
-            field.window?.makeFirstResponder(field)
+        for delay in [0.0, 0.03, 0.08, 0.16] {
+            DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak field] in
+                guard let field,
+                      let window = field.window,
+                      window.isVisible,
+                      window.firstResponder !== field
+                else { return }
+                window.makeKeyAndOrderFront(nil)
+                window.orderFrontRegardless()
+                window.makeFirstResponder(field)
+            }
         }
     }
 
@@ -1578,6 +1656,8 @@
 private final class QuickSearchNativeTextField: NSTextField {
     var onCommand: ((QuickSearchCommand) -> Void)?
 
+    override var acceptsFirstResponder: Bool { true }
+
     override func keyDown(with event: NSEvent) {
         switch Int(event.keyCode) {
         case kVK_UpArrow:

--
Gitblit v1.9.3