From 6f0ede789115d393e1236d98776e91fb52c90c79 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Fri, 12 Jun 2026 14:52:37 +0800
Subject: [PATCH] Add Quick Switch settings entry

---
 C1.source/Sources/Aligner/QuickSwitchRootView.swift |  115 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 115 insertions(+), 0 deletions(-)

diff --git a/C1.source/Sources/Aligner/QuickSwitchRootView.swift b/C1.source/Sources/Aligner/QuickSwitchRootView.swift
index e001bc4..45ea62f 100644
--- a/C1.source/Sources/Aligner/QuickSwitchRootView.swift
+++ b/C1.source/Sources/Aligner/QuickSwitchRootView.swift
@@ -24,6 +24,9 @@
     private lazy var closeConfirmationLayers = makeCloseConfirmationLayers()
     private let closeFeedbackLayer = CALayer()
     private let closeFeedbackTextLayer = CATextLayer()
+    private let settingsButtonLayer = CALayer()
+    private let settingsButtonIconLayer = CALayer()
+    private let settingsButtonFallbackTextLayer = CATextLayer()
     private let appIconProvider: any AppIconProviderProtocol = WorkspaceAppIconProvider()
     private let skeletonThumbnailProvider = NativeSkeletonThumbnailProvider(size: NSSize(width: 160, height: 96))
     private var screenshotSourcesByWindowID: [UInt32: ScreenshotResolutionSource] = [:]
@@ -49,6 +52,7 @@
     private var closeConfirmationRequired = true
     private var closeFeedback: CloseFeedback?
     private var closeFeedbackHideTask: Task<Void, Never>?
+    private var isSettingsButtonHovered = false
     private var hoverTarget: HoverTarget = .none
     private var hoverTargetSource: HoverTargetSource = .event
     private var hoveredSpaceLaneID: UInt64?
@@ -91,6 +95,7 @@
     var onCommitSelection: ((QuickSwitchSelection, QuickSwitchCommitSource) -> Void)?
     var onSpaceLaneClick: ((UInt64, Int) -> Void)?
     var onBackgroundClick: (() -> Bool)?
+    var onOpenSettings: (() -> Void)?
     var onRequestClose: ((QuickSwitchCloseRequest) -> Void)?
     var onCloseConfirmationDisabled: (() -> Void)?
 
@@ -481,6 +486,17 @@
     override func mouseMoved(with event: NSEvent) {
         guard debugHoveredAppGroupIndex == nil else { return }
 
+        let settingsHovered = settingsButtonContains(windowLocation: event.locationInWindow)
+        if isSettingsButtonHovered != settingsHovered {
+            isSettingsButtonHovered = settingsHovered
+            needsLayout = true
+        }
+        if settingsHovered {
+            hoveredCloseTarget = nil
+            setHoverTarget(.none)
+            return
+        }
+
         let closeTarget = closeButtonTarget(at: event.locationInWindow)
         if hoveredCloseTarget != closeTarget {
             hoveredCloseTarget = closeTarget
@@ -491,6 +507,7 @@
 
     override func mouseExited(with event: NSEvent) {
         guard debugHoveredAppGroupIndex == nil else { return }
+        isSettingsButtonHovered = false
         hoveredCloseTarget = nil
         setHoverTarget(.none)
     }
@@ -506,6 +523,11 @@
         }
 
         if handleCloseConfirmationMouseDown(at: event.locationInWindow) {
+            return
+        }
+
+        if settingsButtonContains(windowLocation: event.locationInWindow) {
+            clickSettingsButton(source: "mouse")
             return
         }
 
@@ -1060,6 +1082,9 @@
             "activeSpaceFocusID": activeSpaceFocusID ?? NSNull(),
             "spaceFilterLockedSpaceID": currentViewModel?.lockedSpaceID ?? NSNull(),
             "spaceFilterActive": currentViewModel?.lockedSpaceID != nil,
+            "settingsButtonVisible": !settingsButtonLayer.isHidden,
+            "settingsButtonHovered": isSettingsButtonHovered,
+            "settingsButtonFrame": dictionary(from: settingsButtonLayer.frame),
             "spaceFocusAppGroupIndexes": spaceFocusedAppGroupIndexes(),
             "appHoverAssociatedSpaceIDs": appHoverAssociatedSpaceIDs.sorted(),
             "appHoverAssociatedSpaceLabels": appHoverAssociatedSpaceLabels,
@@ -1302,6 +1327,9 @@
         case "click-background":
             recordMouseCommand("click-background")
             _ = onBackgroundClick?()
+        case "click-settings":
+            recordMouseCommand("click-settings")
+            clickSettingsButton(source: "debug")
         case "hover-card" where parts.count == 3:
             guard let appGroupIndex = Int(parts[1]), let windowIndex = Int(parts[2]) else {
                 recordMouseCommand("invalid:\(command)")
@@ -2244,6 +2272,26 @@
         closeFeedbackTextLayer.truncationMode = .end
         closeFeedbackLayer.addSublayer(closeFeedbackTextLayer)
         glassLayer.addSublayer(closeFeedbackLayer)
+
+        settingsButtonLayer.cornerRadius = 10
+        settingsButtonLayer.masksToBounds = false
+        settingsButtonLayer.borderWidth = 1
+        settingsButtonLayer.shadowColor = NSColor.black.cgColor
+        settingsButtonLayer.shadowOffset = CGSize(width: 0, height: -2)
+        settingsButtonLayer.zPosition = 170
+        settingsButtonIconLayer.contentsGravity = .resizeAspect
+        settingsButtonIconLayer.contentsScale = backingScaleFactor
+        settingsButtonIconLayer.contents = settingsIconImage()
+        settingsButtonFallbackTextLayer.contentsScale = backingScaleFactor
+        settingsButtonFallbackTextLayer.string = "⚙"
+        settingsButtonFallbackTextLayer.font = NSFont.systemFont(ofSize: 18, weight: .regular)
+        settingsButtonFallbackTextLayer.fontSize = 18
+        settingsButtonFallbackTextLayer.alignmentMode = .center
+        settingsButtonFallbackTextLayer.foregroundColor = NSColor.secondaryLabelColor.cgColor
+        settingsButtonFallbackTextLayer.isHidden = settingsButtonIconLayer.contents != nil
+        settingsButtonLayer.addSublayer(settingsButtonIconLayer)
+        settingsButtonLayer.addSublayer(settingsButtonFallbackTextLayer)
+        glassLayer.addSublayer(settingsButtonLayer)
     }
 
     private func rebuildAppShelfItems() {
@@ -2581,8 +2629,40 @@
         layoutWaterfallColumns()
         layoutCloseConfirmation()
         layoutCloseFeedback()
+        layoutSettingsButton()
 
         CATransaction.commit()
+    }
+
+    private func layoutSettingsButton() {
+        let size: CGFloat = 34
+        let topMargin = topSafeAreaInset + RootLayoutMetrics.topContentMargin
+        let x = max(
+            RootLayoutMetrics.horizontalMargin,
+            glassLayer.bounds.width - RootLayoutMetrics.horizontalMargin - size
+        )
+        let y = max(
+            RootLayoutMetrics.bottomMargin,
+            glassLayer.bounds.height - topMargin - size
+        )
+        settingsButtonLayer.frame = CGRect(x: x, y: y, width: size, height: size)
+        settingsButtonLayer.backgroundColor = NSColor.windowBackgroundColor
+            .withAlphaComponent(isSettingsButtonHovered ? 0.78 : 0.54)
+            .cgColor
+        settingsButtonLayer.borderColor = NSColor.separatorColor
+            .withAlphaComponent(isSettingsButtonHovered ? 0.42 : 0.26)
+            .cgColor
+        settingsButtonLayer.shadowOpacity = isSettingsButtonHovered ? 0.14 : 0.08
+        settingsButtonLayer.shadowRadius = isSettingsButtonHovered ? 9 : 6
+        settingsButtonIconLayer.contentsScale = backingScaleFactor
+        settingsButtonFallbackTextLayer.contentsScale = backingScaleFactor
+        let iconInset: CGFloat = 8
+        settingsButtonIconLayer.frame = settingsButtonLayer.bounds.insetBy(dx: iconInset, dy: iconInset)
+        settingsButtonFallbackTextLayer.frame = settingsButtonLayer.bounds.insetBy(dx: 0, dy: 6)
+        settingsButtonFallbackTextLayer.foregroundColor = (isSettingsButtonHovered
+            ? NSColor.labelColor
+            : NSColor.secondaryLabelColor
+        ).cgColor
     }
 
     private var topSafeAreaInset: CGFloat {
@@ -2896,6 +2976,26 @@
 
     private func spaceLaneSegment(spaceID: UInt64) -> SpaceLaneSegmentLayers? {
         spaceLaneSegments.first { $0.space.id == spaceID }
+    }
+
+    private func settingsButtonContains(windowLocation: CGPoint) -> Bool {
+        let locationInView = convert(windowLocation, from: nil)
+        let locationInGlass = CGPoint(
+            x: locationInView.x - glassLayer.frame.minX,
+            y: locationInView.y - glassLayer.frame.minY
+        )
+        return settingsButtonLayer.frame.contains(locationInGlass)
+    }
+
+    private func clickSettingsButton(source: String) {
+        DevelopmentDiagnostics.log("quickSwitch.view.clickSettings", [
+            "source": source
+        ])
+        lastMouseCommand = "click-settings"
+        if !mouseCommandsApplied.contains("click-settings") {
+            mouseCommandsApplied.append("click-settings")
+        }
+        onOpenSettings?()
     }
 
     private func appShelfItem(at windowLocation: CGPoint) -> AppShelfItemLayers? {
@@ -4381,6 +4481,21 @@
         return shapeLayer
     }
 
+    private func settingsIconImage() -> CGImage? {
+        let names = [
+            NSImage.Name("NSPreferencesGeneral"),
+            NSImage.Name("NSAdvanced")
+        ]
+        for name in names {
+            guard let image = NSImage(named: name) else { continue }
+            var rect = CGRect(origin: .zero, size: image.size)
+            if let cgImage = image.cgImage(forProposedRect: &rect, context: nil, hints: nil) {
+                return cgImage
+            }
+        }
+        return nil
+    }
+
     private func makeTextLayer(
         string: String,
         fontSize: CGFloat,

--
Gitblit v1.9.3