From 7a4f911c64b552081d957e826f32d90b2b11c858 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Thu, 04 Jun 2026 19:38:13 +0800
Subject: [PATCH] Make quick switch clicks activate targets
---
C1.source/Sources/Aligner/QuickSwitchRootView.swift | 140 +++++++++++++++++++++++++++++++++++++---------
1 files changed, 112 insertions(+), 28 deletions(-)
diff --git a/C1.source/Sources/Aligner/QuickSwitchRootView.swift b/C1.source/Sources/Aligner/QuickSwitchRootView.swift
index 4e3051e..046f96a 100644
--- a/C1.source/Sources/Aligner/QuickSwitchRootView.swift
+++ b/C1.source/Sources/Aligner/QuickSwitchRootView.swift
@@ -9,7 +9,10 @@
@MainActor
final class QuickSwitchRootView: NSView {
+ private let backdropBlurView = NSVisualEffectView()
+ private let layerHostView = NSView()
private let backgroundLayer = CAGradientLayer()
+ private let safeAreaFillLayer = CALayer()
private let glassLayer = CALayer()
private let laneLayer = CALayer()
private let spaceLaneContentLayer = CALayer()
@@ -118,6 +121,14 @@
static let maxRows = 2
}
+ private enum RootLayoutMetrics {
+ static let horizontalMargin: CGFloat = 24
+ static let topContentMargin: CGFloat = 24
+ static let shelfGap: CGFloat = 16
+ static let waterfallTopGap: CGFloat = 14
+ static let bottomMargin: CGFloat = 34
+ }
+
private enum WaterfallMetrics {
static let gap: CGFloat = 16
static let columnWidth: CGFloat = 252
@@ -143,12 +154,16 @@
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
+ setupBackdropBlurView()
+ setupLayerHostView()
wantsLayer = true
setupLayers()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
+ setupBackdropBlurView()
+ setupLayerHostView()
wantsLayer = true
setupLayers()
}
@@ -176,6 +191,8 @@
override func layout() {
super.layout()
+ backdropBlurView.frame = bounds
+ layerHostView.frame = bounds
layoutLayers()
}
@@ -342,6 +359,9 @@
if isHoverCommand(command) {
hoverResponseLatencyMilliseconds.append(elapsed)
}
+ if lastCommitSource == .mouse {
+ break
+ }
}
}
@@ -443,7 +463,12 @@
"viewClass": String(describing: Self.self),
"isLayerBacked": wantsLayer,
"bounds": dictionary(from: bounds),
+ "safeAreaTopInset": Double(topSafeAreaInset),
+ "safeAreaFillFrame": dictionary(from: safeAreaFillLayer.frame),
"glassFrame": dictionary(from: glassLayer.frame),
+ "glassCornerRadius": Double(glassLayer.cornerRadius),
+ "glassBorderWidth": Double(glassLayer.borderWidth),
+ "glassShadowOpacity": Double(glassLayer.shadowOpacity),
"spaceLaneFrame": dictionary(from: laneLayer.frame),
"spaceLaneContentWidth": Double(spaceLaneContentWidth),
"spaceLaneVisibleWidth": Double(laneLayer.bounds.width),
@@ -701,7 +726,17 @@
"spaceID": segment.space.id,
"label": segment.space.label
])
- focusSpace(segment.space.id, persistent: true)
+ lastSpaceLaneClickSpaceID = segment.space.id
+
+ guard let firstCard = firstWaterfallCard(inSpaceID: segment.space.id) else {
+ DevelopmentDiagnostics.log("quickSwitch.view.clickSpaceLane.emptySpaceUnsupported", [
+ "spaceID": segment.space.id,
+ "label": segment.space.label
+ ])
+ return
+ }
+
+ commitWindowCard(firstCard, trigger: "spaceLane")
}
private func focusSpace(_ spaceID: UInt64, persistent: Bool) {
@@ -752,12 +787,14 @@
columnSelectionHistory[nextSelection.appGroupIndex] = nextSelection.windowIndex
lastAppShelfClickChangedSelection = previousSelection != nextSelection
ensureWaterfallSelectionVisible(nextSelection, horizontalIntent: .alignWithAppShelf(animated: true))
+ updateHoverState(appGroupIndex: appGroupIndex, windowID: nil, spaceID: nil)
+ commitWindowCard(firstCard, trigger: "appShelf")
} else {
lastAppShelfClickChangedSelection = false
ensureWaterfallColumnVisible(column)
+ updateHoverState(appGroupIndex: appGroupIndex, windowID: nil, spaceID: nil)
+ needsLayout = true
}
- updateHoverState(appGroupIndex: appGroupIndex, windowID: nil, spaceID: nil)
- needsLayout = true
}
private func scrollAppShelf(to targetOffset: CGFloat) {
@@ -799,12 +836,17 @@
private func clickWindowCard(_ card: WaterfallCardLayers) {
hoverWindowCard(card)
+ commitWindowCard(card, trigger: "windowCard")
+ }
+
+ private func commitWindowCard(_ card: WaterfallCardLayers, trigger: String) {
let selection = QuickSwitchSelection(
appGroupIndex: card.item.appGroupIndex,
windowIndex: card.item.windowIndex,
windowID: card.item.window.id
)
- DevelopmentDiagnostics.log("quickSwitch.view.clickWindowCard", [
+ DevelopmentDiagnostics.log("quickSwitch.view.commitMouseWindow", [
+ "trigger": trigger,
"appGroupIndex": selection.appGroupIndex,
"windowIndex": selection.windowIndex,
"windowID": selection.windowID,
@@ -1162,35 +1204,54 @@
value ?? NSNull()
}
+ private func setupBackdropBlurView() {
+ backdropBlurView.material = .fullScreenUI
+ backdropBlurView.blendingMode = .behindWindow
+ backdropBlurView.state = .active
+ backdropBlurView.isEmphasized = false
+ backdropBlurView.frame = bounds
+ backdropBlurView.autoresizingMask = [.width, .height]
+ addSubview(backdropBlurView, positioned: .below, relativeTo: nil)
+ }
+
+ private func setupLayerHostView() {
+ layerHostView.wantsLayer = true
+ layerHostView.frame = bounds
+ layerHostView.autoresizingMask = [.width, .height]
+ layerHostView.layer?.masksToBounds = true
+ layerHostView.layer?.backgroundColor = NSColor.clear.cgColor
+ addSubview(layerHostView, positioned: .above, relativeTo: backdropBlurView)
+ }
+
private func setupLayers() {
- guard let rootLayer = layer else { return }
+ guard let rootLayer = layerHostView.layer else { return }
rootLayer.masksToBounds = true
backgroundLayer.colors = [
- NSColor.windowBackgroundColor.withAlphaComponent(0.72).cgColor,
- NSColor.controlBackgroundColor.withAlphaComponent(0.62).cgColor
+ NSColor.windowBackgroundColor.withAlphaComponent(0.58).cgColor,
+ NSColor.controlBackgroundColor.withAlphaComponent(0.48).cgColor
]
backgroundLayer.startPoint = CGPoint(x: 0.5, y: 1.0)
backgroundLayer.endPoint = CGPoint(x: 0.5, y: 0.0)
rootLayer.addSublayer(backgroundLayer)
- glassLayer.cornerRadius = 24
- glassLayer.backgroundColor = NSColor.windowBackgroundColor.withAlphaComponent(0.72).cgColor
- glassLayer.borderColor = NSColor.separatorColor.withAlphaComponent(0.35).cgColor
- glassLayer.borderWidth = 1
- glassLayer.shadowColor = NSColor.black.cgColor
- glassLayer.shadowOpacity = 0.16
- glassLayer.shadowRadius = 24
- glassLayer.shadowOffset = CGSize(width: 0, height: -8)
+ safeAreaFillLayer.backgroundColor = NSColor.black.cgColor
+ rootLayer.addSublayer(safeAreaFillLayer)
+
+ glassLayer.cornerRadius = 0
+ glassLayer.backgroundColor = NSColor.clear.cgColor
+ glassLayer.borderColor = NSColor.clear.cgColor
+ glassLayer.borderWidth = 0
+ glassLayer.shadowOpacity = 0
rootLayer.addSublayer(glassLayer)
- laneLayer.backgroundColor = NSColor.controlBackgroundColor.withAlphaComponent(0.50).cgColor
+ laneLayer.backgroundColor = NSColor.controlBackgroundColor.withAlphaComponent(0.68).cgColor
laneLayer.cornerRadius = 16
laneLayer.masksToBounds = true
glassLayer.addSublayer(laneLayer)
laneLayer.addSublayer(spaceLaneContentLayer)
- shelfLayer.backgroundColor = NSColor.controlBackgroundColor.withAlphaComponent(0.42).cgColor
+ shelfLayer.backgroundColor = NSColor.clear.cgColor
shelfLayer.cornerRadius = 16
shelfLayer.masksToBounds = true
glassLayer.addSublayer(shelfLayer)
@@ -1282,12 +1343,12 @@
container.name = column.app.name
container.cornerRadius = 14
container.masksToBounds = true
- container.backgroundColor = NSColor.controlBackgroundColor.withAlphaComponent(0.34).cgColor
- container.borderColor = NSColor.separatorColor.withAlphaComponent(0.28).cgColor
+ container.backgroundColor = NSColor.controlBackgroundColor.withAlphaComponent(0.56).cgColor
+ container.borderColor = NSColor.separatorColor.withAlphaComponent(0.34).cgColor
container.borderWidth = 1
let header = CALayer()
- header.backgroundColor = NSColor.controlBackgroundColor.withAlphaComponent(0.58).cgColor
+ header.backgroundColor = NSColor.controlBackgroundColor.withAlphaComponent(0.68).cgColor
let appName = makeTextLayer(
string: column.app.name,
@@ -1459,33 +1520,40 @@
backgroundLayer.frame = bounds
- let horizontalInset = max(36, bounds.width * 0.045)
- let topInset = max(36, bounds.height * 0.055)
- glassLayer.frame = bounds.insetBy(dx: horizontalInset, dy: topInset)
+ let safeAreaTopInset = topSafeAreaInset
+ safeAreaFillLayer.frame = CGRect(
+ x: 0,
+ y: max(0, bounds.height - safeAreaTopInset),
+ width: bounds.width,
+ height: safeAreaTopInset
+ )
+ safeAreaFillLayer.isHidden = safeAreaTopInset <= 0.5
+ glassLayer.frame = bounds
let contentBounds = glassLayer.bounds
- let horizontalMargin: CGFloat = 22
+ let horizontalMargin = RootLayoutMetrics.horizontalMargin
+ let topMargin = safeAreaTopInset + RootLayoutMetrics.topContentMargin
let laneHeight = min(104, max(82, contentBounds.height * 0.12))
let availableContentWidth = contentBounds.width - horizontalMargin * 2
appShelfLayout = appShelfLayout(for: availableContentWidth)
let shelfHeight = appShelfHeight(forRows: appShelfLayout.rows)
laneLayer.frame = CGRect(
x: horizontalMargin,
- y: contentBounds.height - horizontalMargin - laneHeight,
+ y: contentBounds.height - topMargin - laneHeight,
width: availableContentWidth,
height: laneHeight
)
shelfLayer.frame = CGRect(
x: horizontalMargin,
- y: laneLayer.frame.minY - 16 - shelfHeight,
+ y: laneLayer.frame.minY - RootLayoutMetrics.shelfGap - shelfHeight,
width: laneLayer.frame.width,
height: shelfHeight
)
waterfallLayer.frame = CGRect(
x: shelfLayer.frame.minX,
- y: 34,
+ y: RootLayoutMetrics.bottomMargin,
width: shelfLayer.frame.width,
- height: shelfLayer.frame.minY - 48
+ height: max(1, shelfLayer.frame.minY - RootLayoutMetrics.bottomMargin - RootLayoutMetrics.waterfallTopGap)
)
layoutSpaceLaneSegments()
@@ -1493,6 +1561,10 @@
layoutWaterfallColumns()
CATransaction.commit()
+ }
+
+ private var topSafeAreaInset: CGFloat {
+ max(0, window?.screen?.safeAreaInsets.top ?? safeAreaInsets.top)
}
private func appShelfLayout(for availableWidth: CGFloat) -> AppShelfLayout {
@@ -1882,6 +1954,18 @@
.sorted()
}
+ private func firstWaterfallCard(inSpaceID spaceID: UInt64) -> WaterfallCardLayers? {
+ for column in waterfallColumns.sorted(by: { $0.column.appGroupIndex < $1.column.appGroupIndex }) {
+ if let card = column.cards
+ .sorted(by: { $0.item.windowIndex < $1.item.windowIndex })
+ .first(where: { $0.item.primarySpaceID == spaceID }) {
+ return card
+ }
+ }
+
+ return nil
+ }
+
private func spaceFocusWindowCount(for appGroupIndex: Int) -> Int {
guard let activeSpaceFocusID else { return 0 }
--
Gitblit v1.9.3