| | |
| | | import CoreGraphics |
| | | import Foundation |
| | | |
| | | public struct QuickSwitchViewModel: Equatable, Sendable { |
| | |
| | | public let windowCount: Int |
| | | public let appCount: Int |
| | | public let appNames: [String] |
| | | public let splitViewAppNames: [String] |
| | | |
| | | public init( |
| | | id: UInt64, |
| | |
| | | isCurrent: Bool = false, |
| | | windowCount: Int, |
| | | appCount: Int, |
| | | appNames: [String] = [] |
| | | appNames: [String] = [], |
| | | splitViewAppNames: [String] = [] |
| | | ) { |
| | | self.id = id |
| | | self.displayUUID = displayUUID |
| | |
| | | self.windowCount = windowCount |
| | | self.appCount = appCount |
| | | self.appNames = appNames |
| | | self.splitViewAppNames = splitViewAppNames |
| | | } |
| | | } |
| | | |
| | |
| | | let windowCountsBySpace = countWindowsBySpace(windowItems) |
| | | let appCountsBySpace = countAppsBySpace(snapshot.appGroups) |
| | | let appNamesBySpace = appNamesBySpace(snapshot.appGroups) |
| | | let splitViewAppNamesBySpace = splitViewAppNamesBySpace(snapshot.appGroups) |
| | | let displays = displayViewModels( |
| | | from: snapshot.displays, |
| | | currentSpaceIDs: currentSpaceIDs, |
| | | windowCountsBySpace: windowCountsBySpace, |
| | | appCountsBySpace: appCountsBySpace, |
| | | appNamesBySpace: appNamesBySpace |
| | | appNamesBySpace: appNamesBySpace, |
| | | splitViewAppNamesBySpace: splitViewAppNamesBySpace |
| | | ) |
| | | let appShelf = appShelfViewModels(from: snapshot.appGroups) |
| | | |
| | |
| | | currentSpaceIDs: Set<UInt64>, |
| | | windowCountsBySpace: [UInt64: Int], |
| | | appCountsBySpace: [UInt64: Int], |
| | | appNamesBySpace: [UInt64: [String]] |
| | | appNamesBySpace: [UInt64: [String]], |
| | | splitViewAppNamesBySpace: [UInt64: [String]] |
| | | ) -> [QuickSwitchDisplayViewModel] { |
| | | let sortedDisplays = displays |
| | | .sorted { $0.uuid.localizedStandardCompare($1.uuid) == .orderedAscending } |
| | |
| | | isCurrent: currentSpaceIDs.contains(space.id), |
| | | windowCount: windowCountsBySpace[space.id] ?? 0, |
| | | appCount: appCountsBySpace[space.id] ?? 0, |
| | | appNames: appNamesBySpace[space.id] ?? [] |
| | | appNames: appNamesBySpace[space.id] ?? [], |
| | | splitViewAppNames: splitViewAppNamesBySpace[space.id] ?? [] |
| | | ) |
| | | } |
| | | |
| | |
| | | return namesBySpace |
| | | } |
| | | |
| | | private static func splitViewAppNamesBySpace(_ appGroups: [QuickSwitchAppGroup]) -> [UInt64: [String]] { |
| | | struct Candidate { |
| | | let appName: String |
| | | let windowID: UInt32 |
| | | let frame: CGRect |
| | | } |
| | | |
| | | var candidatesBySpace: [UInt64: [Candidate]] = [:] |
| | | |
| | | for group in appGroups { |
| | | for item in group.windows { |
| | | guard item.window.isFullscreen, |
| | | let primarySpaceID = item.primarySpaceID, |
| | | let frame = item.window.frame, |
| | | frame.width > 0, |
| | | frame.height > 0 |
| | | else { |
| | | continue |
| | | } |
| | | |
| | | candidatesBySpace[primarySpaceID, default: []].append( |
| | | Candidate( |
| | | appName: group.app.name, |
| | | windowID: item.window.id, |
| | | frame: frame |
| | | ) |
| | | ) |
| | | } |
| | | } |
| | | |
| | | return candidatesBySpace.compactMapValues { candidates in |
| | | guard candidates.count >= 2 else { return nil } |
| | | |
| | | return candidates |
| | | .sorted { lhs, rhs in |
| | | if lhs.frame.midX != rhs.frame.midX { |
| | | return lhs.frame.midX < rhs.frame.midX |
| | | } |
| | | if lhs.frame.midY != rhs.frame.midY { |
| | | return lhs.frame.midY < rhs.frame.midY |
| | | } |
| | | return lhs.windowID < rhs.windowID |
| | | } |
| | | .prefix(2) |
| | | .map(\.appName) |
| | | } |
| | | } |
| | | |
| | | private static func orderedUnique<T: Hashable>(_ values: [T]) -> [T] { |
| | | var seen = Set<T>() |
| | | var result: [T] = [] |