From 9c898b0ca61c090e5e327f750f6c73e28475e10b Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Fri, 12 Jun 2026 19:30:19 +0800
Subject: [PATCH] Fix space filter visual and close verification
---
C1.source/Sources/AlignerCore/Windows/QuickSwitchViewModel.swift | 179 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 files changed, 169 insertions(+), 10 deletions(-)
diff --git a/C1.source/Sources/AlignerCore/Windows/QuickSwitchViewModel.swift b/C1.source/Sources/AlignerCore/Windows/QuickSwitchViewModel.swift
index 49adc7b..1bec9df 100644
--- a/C1.source/Sources/AlignerCore/Windows/QuickSwitchViewModel.swift
+++ b/C1.source/Sources/AlignerCore/Windows/QuickSwitchViewModel.swift
@@ -5,17 +5,20 @@
public let appShelf: [QuickSwitchAppShelfItemViewModel]
public let waterfallColumns: [QuickSwitchWaterfallColumnViewModel]
public let initialSelection: QuickSwitchSelection?
+ public let lockedSpaceID: UInt64?
public init(
displays: [QuickSwitchDisplayViewModel],
appShelf: [QuickSwitchAppShelfItemViewModel],
waterfallColumns: [QuickSwitchWaterfallColumnViewModel],
- initialSelection: QuickSwitchSelection?
+ initialSelection: QuickSwitchSelection?,
+ lockedSpaceID: UInt64? = nil
) {
self.displays = displays
self.appShelf = appShelf
self.waterfallColumns = waterfallColumns
self.initialSelection = initialSelection
+ self.lockedSpaceID = lockedSpaceID
}
public var spaceCount: Int {
@@ -197,7 +200,8 @@
displays: displays,
appShelf: appShelf,
waterfallColumns: columns,
- initialSelection: QuickSwitchFocusPolicy.initialSelection(in: snapshot)
+ initialSelection: QuickSwitchFocusPolicy.initialSelection(in: snapshot),
+ lockedSpaceID: nil
)
}
@@ -208,8 +212,11 @@
appCountsBySpace: [UInt64: Int],
appNamesBySpace: [UInt64: [String]]
) -> [QuickSwitchDisplayViewModel] {
- displays
+ let sortedDisplays = displays
.sorted { $0.uuid.localizedStandardCompare($1.uuid) == .orderedAscending }
+ let displayCount = sortedDisplays.count
+
+ return sortedDisplays
.enumerated()
.map { displayIndex, display in
let displayLabel = displayLabel(for: displayIndex)
@@ -220,7 +227,7 @@
id: space.id,
displayUUID: space.displayUUID,
index: space.index,
- label: "\(displayLabel)\(space.index)",
+ label: spaceLabel(for: space, displayIndex: displayIndex, displayCount: displayCount),
type: space.type,
isCurrent: currentSpaceIDs.contains(space.id),
windowCount: windowCountsBySpace[space.id] ?? 0,
@@ -256,14 +263,13 @@
private static func labelsBySpaceID(from displays: [AlignerDisplay]) -> [UInt64: String] {
var labels: [UInt64: String] = [:]
-
- for (displayIndex, display) in displays
+ let sortedDisplays = displays
.sorted(by: { $0.uuid.localizedStandardCompare($1.uuid) == .orderedAscending })
- .enumerated()
- {
- let displayLabel = displayLabel(for: displayIndex)
+ let displayCount = sortedDisplays.count
+
+ for (displayIndex, display) in sortedDisplays.enumerated() {
for space in display.spaces {
- labels[space.id] = "\(displayLabel)\(space.index)"
+ labels[space.id] = spaceLabel(for: space, displayIndex: displayIndex, displayCount: displayCount)
}
}
@@ -328,4 +334,157 @@
return String(String.UnicodeScalarView(scalars))
}
+
+ private static func spaceLabel(for space: AlignerSpace, displayIndex: Int, displayCount: Int) -> String {
+ if displayCount <= 1 {
+ return String(format: "%02d", space.index)
+ }
+
+ return "\(displayLabel(for: displayIndex))\(space.index)"
+ }
+}
+
+public enum QuickSwitchSpaceFilterPolicy {
+ public static func projectedViewModel(
+ from viewModel: QuickSwitchViewModel,
+ lockedSpaceID: UInt64?,
+ preferredSelection: QuickSwitchSelection? = nil
+ ) -> QuickSwitchViewModel {
+ guard let lockedSpaceID else {
+ return replacingInitialSelection(
+ in: viewModel,
+ lockedSpaceID: nil,
+ preferredSelection: preferredSelection
+ )
+ }
+
+ var nextGlobalIndex = 0
+ let columns = viewModel.waterfallColumns.compactMap { column -> QuickSwitchWaterfallColumnViewModel? in
+ let windows = column.windows.filter { $0.primarySpaceID == lockedSpaceID }
+ guard !windows.isEmpty else { return nil }
+
+ let reindexedWindows = windows.enumerated().map { windowIndex, card in
+ defer { nextGlobalIndex += 1 }
+ return QuickSwitchWindowCardViewModel(
+ appGroupIndex: column.appGroupIndex,
+ windowIndex: windowIndex,
+ globalIndex: nextGlobalIndex,
+ window: card.window,
+ primarySpaceID: card.primarySpaceID,
+ primarySpaceLabel: card.primarySpaceLabel
+ )
+ }
+
+ return QuickSwitchWaterfallColumnViewModel(
+ appGroupIndex: column.appGroupIndex,
+ app: column.app,
+ windows: reindexedWindows
+ )
+ }
+
+ let columnsByAppGroupIndex = Dictionary(uniqueKeysWithValues: columns.map { ($0.appGroupIndex, $0) })
+ let appShelf = viewModel.appShelf.compactMap { item -> QuickSwitchAppShelfItemViewModel? in
+ guard let column = columnsByAppGroupIndex[item.appGroupIndex] else { return nil }
+ return QuickSwitchAppShelfItemViewModel(
+ appGroupIndex: item.appGroupIndex,
+ app: item.app,
+ windowCount: column.windows.count,
+ primarySpaceIDs: orderedPrimarySpaceIDs(in: column.windows),
+ hasMinimizedWindows: column.windows.contains { $0.window.isMinimized },
+ hasFullscreenWindows: column.windows.contains { $0.window.isFullscreen }
+ )
+ }
+
+ return QuickSwitchViewModel(
+ displays: viewModel.displays,
+ appShelf: appShelf,
+ waterfallColumns: columns,
+ initialSelection: initialSelection(in: columns, preferredSelection: preferredSelection),
+ lockedSpaceID: lockedSpaceID
+ )
+ }
+
+ public static func singleFullscreenSelection(
+ inSpaceID spaceID: UInt64,
+ viewModel: QuickSwitchViewModel
+ ) -> QuickSwitchSelection? {
+ guard viewModel.displays
+ .flatMap(\.spaces)
+ .contains(where: { $0.id == spaceID && $0.type == .fullscreen })
+ else {
+ return nil
+ }
+
+ let cards = viewModel.waterfallColumns
+ .flatMap(\.windows)
+ .filter { $0.primarySpaceID == spaceID }
+
+ guard cards.count == 1,
+ let card = cards.first,
+ card.window.isFullscreen
+ else {
+ return nil
+ }
+
+ return QuickSwitchSelection(
+ appGroupIndex: card.appGroupIndex,
+ windowIndex: card.windowIndex,
+ windowID: card.window.id
+ )
+ }
+
+ private static func replacingInitialSelection(
+ in viewModel: QuickSwitchViewModel,
+ lockedSpaceID: UInt64?,
+ preferredSelection: QuickSwitchSelection?
+ ) -> QuickSwitchViewModel {
+ QuickSwitchViewModel(
+ displays: viewModel.displays,
+ appShelf: viewModel.appShelf,
+ waterfallColumns: viewModel.waterfallColumns,
+ initialSelection: initialSelection(in: viewModel.waterfallColumns, preferredSelection: preferredSelection)
+ ?? viewModel.initialSelection,
+ lockedSpaceID: lockedSpaceID
+ )
+ }
+
+ private static func initialSelection(
+ in columns: [QuickSwitchWaterfallColumnViewModel],
+ preferredSelection: QuickSwitchSelection?
+ ) -> QuickSwitchSelection? {
+ if let preferredSelection,
+ let matchingCard = columns
+ .flatMap(\.windows)
+ .first(where: { $0.window.id == preferredSelection.windowID }) {
+ return QuickSwitchSelection(
+ appGroupIndex: matchingCard.appGroupIndex,
+ windowIndex: matchingCard.windowIndex,
+ windowID: matchingCard.window.id
+ )
+ }
+
+ guard let firstCard = columns.first?.windows.first else { return nil }
+ return QuickSwitchSelection(
+ appGroupIndex: firstCard.appGroupIndex,
+ windowIndex: firstCard.windowIndex,
+ windowID: firstCard.window.id
+ )
+ }
+
+ private static func orderedPrimarySpaceIDs(in cards: [QuickSwitchWindowCardViewModel]) -> [UInt64] {
+ var seen = Set<UInt64>()
+ var result: [UInt64] = []
+
+ for card in cards {
+ guard let spaceID = card.primarySpaceID,
+ !seen.contains(spaceID)
+ else {
+ continue
+ }
+ seen.insert(spaceID)
+ result.append(spaceID)
+ }
+
+ return result
+ }
}
--
Gitblit v1.9.3