Ariver
2026-06-11 72219babd9bf0a4ce6fc26af91ee969a8705f053
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
    }
}