From cba722f8a207982ade9e941eca3e6070e8705749 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Sat, 20 Jun 2026 13:01:49 +0800
Subject: [PATCH] Commit single-window app shortcuts on first key
---
C1.source/Sources/Aligner/QuickSwitchRootView.swift | 294 +++++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 files changed, 266 insertions(+), 28 deletions(-)
diff --git a/C1.source/Sources/Aligner/QuickSwitchRootView.swift b/C1.source/Sources/Aligner/QuickSwitchRootView.swift
index f137091..3b0a67e 100644
--- a/C1.source/Sources/Aligner/QuickSwitchRootView.swift
+++ b/C1.source/Sources/Aligner/QuickSwitchRootView.swift
@@ -99,6 +99,8 @@
private var windowIndexKeyPendingClearTask: Task<Void, Never>?
private var lastWindowIndexKeyCommand: String?
private var lastWindowIndexKeyCommitCode: String?
+ private var windowShortcutFilterPhase: WindowShortcutFilterPhase = .none
+ private var lastWindowShortcutPreCommitFilterSnapshot: WindowShortcutPreCommitFilterSnapshot?
private var lastBoundaryBounceAxis: String?
private var lastBoundaryBounceDirection: String?
private var boundaryBounceCount = 0
@@ -343,6 +345,47 @@
let appSymbol: String
let appGroupIndex: Int
let startedAt: CFTimeInterval
+ }
+
+ private enum WindowShortcutFilterPhase: String {
+ case none
+ case appPending
+ case targetPreCommit
+ case invalid
+ case expired
+ }
+
+ private enum WindowShortcutFilterState: String {
+ case none
+ case matched
+ case dimmed
+ case target
+ }
+
+ private struct WindowShortcutPreCommitFilterSnapshot {
+ let targetCode: String
+ let targetWindowID: UInt32
+ let matchedWindowIDs: [UInt32]
+ let dimmedWindowIDs: [UInt32]
+
+ var reportDictionary: [String: Any] {
+ [
+ "phase": WindowShortcutFilterPhase.targetPreCommit.rawValue,
+ "targetCode": targetCode,
+ "targetWindowID": Int(targetWindowID),
+ "matchedWindowIDs": matchedWindowIDs.map(Int.init),
+ "dimmedWindowIDs": dimmedWindowIDs.map(Int.init)
+ ]
+ }
+ }
+
+ private struct WindowShortcutFilterReport {
+ let active: Bool
+ let phase: WindowShortcutFilterPhase
+ let pendingAppGroupIndex: Int?
+ let prefix: String?
+ let matchedWindowIDs: [UInt32]
+ let dimmedWindowIDs: [UInt32]
}
private struct ProjectionLayerSnapshot {
@@ -1087,6 +1130,8 @@
clearWindowIndexKeyPending()
lastWindowIndexKeyCommand = nil
lastWindowIndexKeyCommitCode = nil
+ windowShortcutFilterPhase = .none
+ lastWindowShortcutPreCommitFilterSnapshot = nil
lastBoundaryBounceAxis = nil
lastBoundaryBounceDirection = nil
boundaryBounceCount = 0
@@ -1405,6 +1450,7 @@
let firstFrameGatePassed = firstFrameP95.map { $0 < 150 } ?? false
let keyboardGatePassed = keyboardP95.map { $0 < 50 } ?? false
let hoverGatePassed = hoverP95.map { $0 <= 32 } ?? false
+ let shortcutFilterReport = makeWindowShortcutFilterReport()
return [
"viewClass": String(describing: Self.self),
@@ -1535,6 +1581,15 @@
"windowIndexKeyPendingAppSymbol": windowIndexKeyPending?.appSymbol ?? NSNull(),
"lastWindowIndexKeyCommand": lastWindowIndexKeyCommand ?? NSNull(),
"lastWindowIndexKeyCommitCode": lastWindowIndexKeyCommitCode ?? NSNull(),
+ "windowShortcutFilterActive": shortcutFilterReport.active,
+ "windowShortcutFilterPhase": shortcutFilterReport.phase.rawValue,
+ "windowShortcutFilterPendingAppGroupIndex": shortcutFilterReport.pendingAppGroupIndex ?? NSNull(),
+ "windowShortcutFilterPrefix": shortcutFilterReport.prefix ?? NSNull(),
+ "windowShortcutFilterMatchedCount": shortcutFilterReport.matchedWindowIDs.count,
+ "windowShortcutFilterDimmedCount": shortcutFilterReport.dimmedWindowIDs.count,
+ "windowShortcutFilterMatchedWindowIDs": shortcutFilterReport.matchedWindowIDs.map(Int.init),
+ "windowShortcutFilterDimmedWindowIDs": shortcutFilterReport.dimmedWindowIDs.map(Int.init),
+ "lastWindowShortcutPreCommitFilterSnapshot": lastWindowShortcutPreCommitFilterSnapshot?.reportDictionary ?? NSNull(),
"lastBoundaryBounceAxis": lastBoundaryBounceAxis ?? NSNull(),
"lastBoundaryBounceDirection": lastBoundaryBounceDirection ?? NSNull(),
"boundaryBounceCount": boundaryBounceCount,
@@ -1708,7 +1763,12 @@
recordKeyboardCommand("window:\(code)")
lastWindowIndexKeyCommand = "window:\(code)"
lastWindowIndexKeyCommitCode = code
- clearWindowIndexKeyPending()
+ lastWindowShortcutPreCommitFilterSnapshot = makeWindowShortcutPreCommitFilterSnapshot(
+ target: card,
+ code: code
+ )
+ windowShortcutFilterPhase = .targetPreCommit
+ clearWindowIndexKeyPending(resetShortcutFilter: false)
commitKeyboardWindowCard(card, trigger: "windowIndexKey")
return true
}
@@ -1716,8 +1776,10 @@
recordKeyboardCommand("windowInvalid:\(code)")
lastWindowIndexKeyCommand = "windowInvalid:\(code)"
lastWindowIndexKeyCommitCode = nil
+ windowShortcutFilterPhase = .invalid
+ lastWindowShortcutPreCommitFilterSnapshot = nil
selectionChangedByLastCommand = false
- clearWindowIndexKeyPending()
+ clearWindowIndexKeyPending(resetShortcutFilter: false)
return true
}
@@ -1728,11 +1790,21 @@
if focusAppShelfItemForKeyboard(indexSymbol: symbol, commandLabel: recordedCommand),
let ordinal = Self.appShelfIndexSymbols.firstIndex(of: symbol),
ordinal < appShelfItems.count {
- setWindowIndexKeyPending(
- appSymbol: symbol,
- appGroupIndex: appShelfItems[ordinal].item.appGroupIndex,
- startedAt: now
- )
+ let appGroupIndex = appShelfItems[ordinal].item.appGroupIndex
+ if let card = singleVisibleWaterfallCard(appGroupIndex: appGroupIndex) {
+ recordKeyboardCommand("windowSingle:\(symbol)")
+ lastWindowIndexKeyCommand = "windowSingle:\(symbol)"
+ lastWindowIndexKeyCommitCode = symbol
+ lastWindowShortcutPreCommitFilterSnapshot = nil
+ clearWindowIndexKeyPending()
+ commitKeyboardWindowCard(card, trigger: "windowIndexSingleApp")
+ } else {
+ setWindowIndexKeyPending(
+ appSymbol: symbol,
+ appGroupIndex: appGroupIndex,
+ startedAt: now
+ )
+ }
} else {
selectionChangedByLastCommand = false
clearWindowIndexKeyPending()
@@ -1783,6 +1855,9 @@
appGroupIndex: appGroupIndex,
startedAt: startedAt
)
+ windowShortcutFilterPhase = .appPending
+ lastWindowShortcutPreCommitFilterSnapshot = nil
+ needsLayout = true
windowIndexKeyPendingClearTask = Task { @MainActor [weak self] in
do {
try await Task.sleep(nanoseconds: UInt64(Self.windowIndexKeyPendingInterval * 1_000_000_000))
@@ -1799,13 +1874,26 @@
}
self.windowIndexKeyPending = nil
self.windowIndexKeyPendingClearTask = nil
+ self.windowShortcutFilterPhase = .expired
+ self.lastWindowShortcutPreCommitFilterSnapshot = nil
+ self.needsLayout = true
}
}
- private func clearWindowIndexKeyPending() {
+ private func clearWindowIndexKeyPending(resetShortcutFilter: Bool = true) {
+ let hadFilterState = windowIndexKeyPending != nil
+ || windowShortcutFilterPhase != .none
+ || lastWindowShortcutPreCommitFilterSnapshot != nil
windowIndexKeyPendingClearTask?.cancel()
windowIndexKeyPendingClearTask = nil
windowIndexKeyPending = nil
+ if resetShortcutFilter {
+ windowShortcutFilterPhase = .none
+ lastWindowShortcutPreCommitFilterSnapshot = nil
+ }
+ if hadFilterState {
+ needsLayout = true
+ }
}
@discardableResult
@@ -4454,17 +4542,41 @@
)
: nil
- applySpaceLaneContentVisualState(segment, isLocked: isLocked)
+ applySpaceLaneContentVisualState(
+ segment,
+ isLocked: isLocked,
+ usesLightActiveFill: resolvedTheme == .dark && hasWindows && !isLocked && (isActive || isAssociated)
+ )
}
- private func applySpaceLaneContentVisualState(_ segment: SpaceLaneSegmentLayers, isLocked: Bool) {
- let primaryTextColor: NSColor = isLocked ? .white : themePalette.spaceLanePrimaryText
+ private func applySpaceLaneContentVisualState(
+ _ segment: SpaceLaneSegmentLayers,
+ isLocked: Bool,
+ usesLightActiveFill: Bool
+ ) {
+ let primaryTextColor: NSColor = isLocked
+ ? .white
+ : usesLightActiveFill
+ ? NSColor.black.withAlphaComponent(0.82)
+ : themePalette.spaceLanePrimaryText
let secondaryTextColor: NSColor = isLocked
? .white
- : themePalette.spaceLaneSecondaryText
+ : usesLightActiveFill
+ ? NSColor.black.withAlphaComponent(0.58)
+ : themePalette.spaceLaneSecondaryText
+ let markerTextColor: NSColor = isLocked
+ ? .white
+ : usesLightActiveFill
+ ? NSColor.black.withAlphaComponent(0.62)
+ : themePalette.spaceLaneSecondaryText.withAlphaComponent(segment.space.isCurrent ? 0.72 : 0.54)
+ let blockColor: NSColor = isLocked
+ ? .white
+ : usesLightActiveFill
+ ? NSColor.black.withAlphaComponent(0.20)
+ : spaceLaneOccupiedColor.withAlphaComponent(segment.space.isCurrent ? 0.72 : 0.58)
segment.labelLayer.foregroundColor = primaryTextColor.cgColor
- segment.countLayer.foregroundColor = (isLocked ? NSColor.white : themePalette.spaceLaneSecondaryText).cgColor
+ segment.countLayer.foregroundColor = secondaryTextColor.cgColor
segment.appLayer.foregroundColor = (segment.space.type == .fullscreen
? primaryTextColor
: secondaryTextColor
@@ -4474,16 +4586,10 @@
}
for block in segment.windowBlocks {
- block.backgroundColor = (isLocked
- ? NSColor.white
- : spaceLaneOccupiedColor.withAlphaComponent(segment.space.isCurrent ? 0.72 : 0.58)
- ).cgColor
+ block.backgroundColor = blockColor.cgColor
}
- (segment.fullscreenMarkerLayer as? CAShapeLayer)?.strokeColor = (isLocked
- ? NSColor.white
- : themePalette.spaceLaneSecondaryText.withAlphaComponent(segment.space.isCurrent ? 0.72 : 0.54)
- ).cgColor
+ (segment.fullscreenMarkerLayer as? CAShapeLayer)?.strokeColor = markerTextColor.cgColor
}
private func shouldScrollSpaceLane(for event: NSEvent) -> Bool {
@@ -5433,13 +5539,15 @@
let isKeyboardFocused = card.item.window.id == keyboardFocusedWindowID
let isHorizontalAppDefaultFocused = isHorizontalAppDefaultCard(card)
let isAppLinked = isWaterfallCardAppLinked(card)
+ let shortcutFilterState = windowShortcutFilterState(for: card)
applyWaterfallCardVisualState(
card,
selected: shouldShowSelectedVisual(isSelected: isSelected, isHovered: isHovered)
|| isKeyboardFocused
|| isHorizontalAppDefaultFocused,
hovered: isHovered,
- appLinked: isAppLinked
+ appLinked: isAppLinked,
+ shortcutFilterState: shortcutFilterState
)
let outerInset: CGFloat = 0
@@ -5494,7 +5602,7 @@
windowIndex: card.item.windowIndex,
windowID: card.item.window.id
),
- visible: isHovered,
+ visible: isHovered && shortcutFilterState != .dimmed,
anchorTopRight: CGPoint(x: bounds.maxX - 12, y: bounds.maxY - 12)
)
card.shineLayer.frame = CGRect(
@@ -5514,9 +5622,11 @@
_ card: WaterfallCardLayers,
selected: Bool,
hovered: Bool,
- appLinked: Bool
+ appLinked: Bool,
+ shortcutFilterState: WindowShortcutFilterState
) {
let isSpaceFocused = isWaterfallCardSpaceFocused(card)
+ let isShortcutDimmed = shortcutFilterState == .dimmed
card.containerLayer.backgroundColor = (selected
? themePalette.cardSelectedBackground
: hovered
@@ -5527,6 +5637,7 @@
? accentTintedWindowBackground(fraction: 0.10, alpha: 0.94)
: themePalette.cardBackground
).cgColor
+ card.containerLayer.opacity = isShortcutDimmed ? windowShortcutFilterDimOpacity : 1
card.containerLayer.borderColor = (selected
? NSColor.controlAccentColor.withAlphaComponent(0.52)
: hovered
@@ -5540,7 +5651,7 @@
card.containerLayer.borderWidth = (selected || hovered || appLinked || isSpaceFocused) ? 1.5 : 1
card.containerLayer.shadowColor = NSColor.black.cgColor
let darkShadowBoost: Float = resolvedTheme == .dark ? 0.08 : 0
- card.containerLayer.shadowOpacity = selected
+ let baseShadowOpacity: Float = selected
? 0.24 + darkShadowBoost
: hovered
? 0.14 + darkShadowBoost
@@ -5549,6 +5660,7 @@
: isSpaceFocused
? 0.20 + darkShadowBoost
: 0
+ card.containerLayer.shadowOpacity = isShortcutDimmed ? min(baseShadowOpacity, 0.03) : baseShadowOpacity
card.containerLayer.shadowRadius = selected ? 22 : hovered ? 17 : appLinked ? 18 : isSpaceFocused ? 24 : 0
card.containerLayer.shadowOffset = selected
? CGSize(width: 0, height: -6)
@@ -5561,10 +5673,10 @@
: .zero
card.containerLayer.zPosition = selected ? 20 : hovered ? 12 : appLinked ? 10 : isSpaceFocused ? 8 : 0
let focusedScale: CGFloat = selected ? WaterfallMetrics.selectedCardScale : isSpaceFocused ? 1.012 : 1.008
- card.containerLayer.transform = (selected || hovered || isSpaceFocused)
+ card.containerLayer.transform = (!isShortcutDimmed && (selected || hovered || isSpaceFocused))
? CATransform3DMakeScale(focusedScale, focusedScale, 1)
: CATransform3DIdentity
- card.containerLayer.shadowPath = (selected || hovered || appLinked || isSpaceFocused)
+ card.containerLayer.shadowPath = (!isShortcutDimmed && (selected || hovered || appLinked || isSpaceFocused))
? CGPath(
roundedRect: card.containerLayer.bounds,
cornerWidth: card.containerLayer.cornerRadius,
@@ -5580,7 +5692,7 @@
).cgColor
card.titleBarLayer.borderColor = NSColor.clear.cgColor
card.titleBarLayer.borderWidth = 0
- card.shineLayer.opacity = selected ? 1 : 0
+ card.shineLayer.opacity = (!isShortcutDimmed && selected) ? 1 : 0
}
private func layoutCloseButton(
@@ -6320,6 +6432,110 @@
return Self.appShelfIndexSymbols[ordinal]
}
+ private var visibleWaterfallCards: [WaterfallCardLayers] {
+ waterfallColumns.flatMap(\.cards)
+ }
+
+ private func singleVisibleWaterfallCard(appGroupIndex: Int) -> WaterfallCardLayers? {
+ let cards = visibleWaterfallCards.filter { $0.item.appGroupIndex == appGroupIndex }
+ return cards.count == 1 ? cards[0] : nil
+ }
+
+ private var windowShortcutFilterDimOpacity: Float {
+ resolvedTheme == .dark ? 0.44 : 0.38
+ }
+
+ private func windowShortcutFilterState(for card: WaterfallCardLayers) -> WindowShortcutFilterState {
+ switch windowShortcutFilterPhase {
+ case .appPending:
+ guard let pending = windowIndexKeyPending else { return .none }
+ return card.item.appGroupIndex == pending.appGroupIndex ? .matched : .dimmed
+ case .targetPreCommit:
+ guard let snapshot = lastWindowShortcutPreCommitFilterSnapshot else { return .none }
+ if card.item.window.id == snapshot.targetWindowID {
+ return .target
+ }
+ if snapshot.dimmedWindowIDs.contains(card.item.window.id) {
+ return .dimmed
+ }
+ if snapshot.matchedWindowIDs.contains(card.item.window.id) {
+ return .matched
+ }
+ return .none
+ case .none, .invalid, .expired:
+ return .none
+ }
+ }
+
+ private func makeWindowShortcutPreCommitFilterSnapshot(
+ target card: WaterfallCardLayers,
+ code: String
+ ) -> WindowShortcutPreCommitFilterSnapshot {
+ let targetWindowID = card.item.window.id
+ let allWindowIDs = visibleWaterfallCards.map { $0.item.window.id }
+ return WindowShortcutPreCommitFilterSnapshot(
+ targetCode: code,
+ targetWindowID: targetWindowID,
+ matchedWindowIDs: [targetWindowID],
+ dimmedWindowIDs: allWindowIDs.filter { $0 != targetWindowID }.sorted()
+ )
+ }
+
+ private func makeWindowShortcutFilterReport() -> WindowShortcutFilterReport {
+ switch windowShortcutFilterPhase {
+ case .appPending:
+ guard let pending = windowIndexKeyPending else {
+ return emptyWindowShortcutFilterReport(phase: .none)
+ }
+ let cards = visibleWaterfallCards
+ let matched = cards
+ .filter { $0.item.appGroupIndex == pending.appGroupIndex }
+ .map { $0.item.window.id }
+ .sorted()
+ let dimmed = cards
+ .filter { $0.item.appGroupIndex != pending.appGroupIndex }
+ .map { $0.item.window.id }
+ .sorted()
+ return WindowShortcutFilterReport(
+ active: true,
+ phase: .appPending,
+ pendingAppGroupIndex: pending.appGroupIndex,
+ prefix: pending.appSymbol,
+ matchedWindowIDs: matched,
+ dimmedWindowIDs: dimmed
+ )
+ case .targetPreCommit:
+ guard let snapshot = lastWindowShortcutPreCommitFilterSnapshot else {
+ return emptyWindowShortcutFilterReport(phase: .none)
+ }
+ return WindowShortcutFilterReport(
+ active: true,
+ phase: .targetPreCommit,
+ pendingAppGroupIndex: nil,
+ prefix: snapshot.targetCode,
+ matchedWindowIDs: snapshot.matchedWindowIDs,
+ dimmedWindowIDs: snapshot.dimmedWindowIDs
+ )
+ case .invalid, .expired:
+ return emptyWindowShortcutFilterReport(phase: windowShortcutFilterPhase)
+ case .none:
+ return emptyWindowShortcutFilterReport(phase: .none)
+ }
+ }
+
+ private func emptyWindowShortcutFilterReport(
+ phase: WindowShortcutFilterPhase
+ ) -> WindowShortcutFilterReport {
+ WindowShortcutFilterReport(
+ active: false,
+ phase: phase,
+ pendingAppGroupIndex: nil,
+ prefix: nil,
+ matchedWindowIDs: [],
+ dimmedWindowIDs: []
+ )
+ }
+
private func waterfallStateText(for card: QuickSwitchWindowCardViewModel) -> String {
var states: [String] = []
if card.window.isMinimized {
@@ -6826,6 +7042,10 @@
dy: column.containerLayer.frame.minY + column.cardsClipLayer.frame.minY
)
let screenshotSource = screenshotSourcesByWindowID[card.item.window.id]
+ let shortcutFilterState = windowShortcutFilterState(for: card)
+ let shortcutFilterOpacity = shortcutFilterState == .dimmed
+ ? windowShortcutFilterDimOpacity
+ : Float(1)
var report = [
"appGroupIndex": card.item.appGroupIndex,
@@ -6844,6 +7064,8 @@
"isSelected": card.item.window.id == effectiveSelection?.windowID,
"isKeyboardFocused": card.item.window.id == keyboardFocusedWindowID,
"isHorizontalAppDefault": isHorizontalAppDefaultCard(card),
+ "windowShortcutFilterState": shortcutFilterState.rawValue,
+ "windowShortcutFilterOpacity": Double(shortcutFilterOpacity),
"visualStates": waterfallCardVisualStates(for: card),
"thumbnailStrategy": thumbnailStrategyString(for: card.item.window),
"screenshotSource": screenshotSourceString(screenshotSource, for: card.item.window),
@@ -6988,6 +7210,16 @@
if isWaterfallCardSpaceFocused(card) {
states.append("spaceFocused")
}
+ switch windowShortcutFilterState(for: card) {
+ case .matched:
+ states.append("shortcutMatched")
+ case .dimmed:
+ states.append("shortcutDimmed")
+ case .target:
+ states.append("shortcutTarget")
+ case .none:
+ break
+ }
if card.item.window.isMinimized {
states.append("minimized")
}
@@ -7035,6 +7267,10 @@
}
let isAppAssociated = appHoverAssociatedSpaceIDs.contains(space.id)
let isWindowAssociated = space.id == windowHoverAssociatedSpaceID
+ let isLocked = space.id == currentViewModel?.lockedSpaceID
+ let isActive = space.id == activeSpaceFocusID || isLocked
+ let isAssociated = (isAppAssociated || isWindowAssociated) && !isActive
+ let usesLightActiveFill = resolvedTheme == .dark && space.windowCount > 0 && !isLocked && (isActive || isAssociated)
let group = spaceLaneDisplayGroup(containingSpaceID: space.id)
let visibleFrame = segment.containerLayer.frame.offsetBy(
dx: (group?.containerLayer.frame.minX ?? 0) + spaceLaneContentLayer.frame.minX,
@@ -7053,6 +7289,8 @@
"label": space.label,
"type": spaceTypeName(space.type),
"visualStates": visualStates(for: space),
+ "usesLightActiveFill": usesLightActiveFill,
+ "foregroundContrastStyle": usesLightActiveFill ? "darkOnLight" : "themeDefault",
"fullscreenMarkerVisible": segment.fullscreenMarkerLayer != nil,
"fullscreenMarkerKind": fullscreenMarkerKind(
markerLayer: segment.fullscreenMarkerLayer,
--
Gitblit v1.9.3