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
        )
    }
@@ -339,3 +343,148 @@
        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
    }
}