| | |
| | | debugOverlayWidth: debugOverlayWidth |
| | | ) |
| | | private var currentSnapshot: QuickSwitchSnapshot? |
| | | private var sourceViewModel: QuickSwitchViewModel? |
| | | private var currentViewModel: QuickSwitchViewModel? |
| | | private var lockedSpaceFilterID: UInt64? |
| | | private var selectionBeforeSpaceFilter: QuickSwitchSelection? |
| | | private var lastSpaceFilterAction: String? |
| | | private var lastSnapshotError: String? |
| | | private var snapshotTask: Task<Void, Never>? |
| | | private var screenshotTask: Task<Void, Never>? |
| | |
| | | lastCloseError = nil |
| | | pendingCloseVerification = nil |
| | | suppressedCloseTargets = [] |
| | | lockedSpaceFilterID = nil |
| | | selectionBeforeSpaceFilter = nil |
| | | lastSpaceFilterAction = nil |
| | | lastDismissReason = nil |
| | | lastSystemCriticalAction = nil |
| | | lastSystemCriticalMonitorSource = nil |
| | |
| | | } |
| | | view.onCommitSelection = { [weak self] selection, source in |
| | | self?.commitSelection(selection, source: source) |
| | | } |
| | | view.onSpaceLaneClick = { [weak self] spaceID in |
| | | self?.handleSpaceLaneClick(spaceID) |
| | | } |
| | | view.onRequestClose = { [weak self] request in |
| | | self?.requestClose(request) |
| | |
| | | "initialSelectionWindowID": currentViewModel?.initialSelection?.windowID ?? NSNull(), |
| | | "appShelfNames": currentViewModel?.appShelf.map(\.app.name) ?? [], |
| | | "spaceLabels": currentViewModel?.displays.flatMap { $0.spaces.map(\.label) } ?? [], |
| | | "spaceFilterLockedSpaceID": lockedSpaceFilterID ?? NSNull(), |
| | | "spaceFilterActive": lockedSpaceFilterID != nil, |
| | | "lastSpaceFilterAction": lastSpaceFilterAction ?? NSNull(), |
| | | "overlayOpenElapsedMilliseconds": overlayOpenElapsedMilliseconds ?? NSNull(), |
| | | "snapshotStartElapsedMilliseconds": snapshotStartElapsedMilliseconds ?? NSNull(), |
| | | "snapshotDurationMilliseconds": snapshotDurationMilliseconds ?? NSNull(), |
| | |
| | | |
| | | private func clearSnapshotState() { |
| | | currentSnapshot = nil |
| | | sourceViewModel = nil |
| | | currentViewModel = nil |
| | | lastSnapshotError = nil |
| | | } |
| | |
| | | onSnapshotUpdated?() |
| | | } |
| | | |
| | | private func commitSelection(_ selection: QuickSwitchSelection, source: QuickSwitchCommitSource) { |
| | | private func commitSelection( |
| | | _ selection: QuickSwitchSelection, |
| | | source: QuickSwitchCommitSource, |
| | | in viewModel: QuickSwitchViewModel? = nil |
| | | ) { |
| | | DevelopmentDiagnostics.log("quickSwitch.activation.commit.start", [ |
| | | "source": source.rawValue, |
| | | "appGroupIndex": selection.appGroupIndex, |
| | |
| | | lastActivationWindowID = selection.windowID |
| | | lastActivationError = nil |
| | | |
| | | guard let window = window(for: selection) else { |
| | | guard let window = window(for: selection, in: viewModel ?? currentViewModel) else { |
| | | lastActivationResult = .windowNotFound |
| | | DevelopmentDiagnostics.log("quickSwitch.activation.commit.windowNotFound", [ |
| | | "source": source.rawValue, |
| | |
| | | "result": lastActivationResult.map(Self.activationResultString) |
| | | ]) |
| | | } |
| | | onSnapshotUpdated?() |
| | | } |
| | | |
| | | private func handleSpaceLaneClick(_ spaceID: UInt64) { |
| | | guard let sourceViewModel else { |
| | | lastSpaceFilterAction = "blockedNoSourceViewModel" |
| | | DevelopmentDiagnostics.log("quickSwitch.spaceFilter.click.blocked", [ |
| | | "spaceID": spaceID, |
| | | "reason": "noSourceViewModel" |
| | | ]) |
| | | onSnapshotUpdated?() |
| | | return |
| | | } |
| | | |
| | | if let fullscreenSelection = QuickSwitchSpaceFilterPolicy.singleFullscreenSelection( |
| | | inSpaceID: spaceID, |
| | | viewModel: sourceViewModel |
| | | ) { |
| | | lockedSpaceFilterID = nil |
| | | selectionBeforeSpaceFilter = nil |
| | | lastSpaceFilterAction = "activateSingleFullscreen" |
| | | let visibleViewModel = projectedViewModel(from: sourceViewModel) |
| | | currentViewModel = visibleViewModel |
| | | view.applyProjected(viewModel: visibleViewModel) |
| | | DevelopmentDiagnostics.log("quickSwitch.spaceFilter.singleFullscreenActivate", [ |
| | | "spaceID": spaceID, |
| | | "windowID": fullscreenSelection.windowID |
| | | ]) |
| | | commitSelection(fullscreenSelection, source: .mouse, in: sourceViewModel) |
| | | return |
| | | } |
| | | |
| | | let preferredSelection: QuickSwitchSelection? |
| | | if lockedSpaceFilterID == spaceID { |
| | | lockedSpaceFilterID = nil |
| | | preferredSelection = selectionBeforeSpaceFilter |
| | | selectionBeforeSpaceFilter = nil |
| | | lastSpaceFilterAction = "unlock" |
| | | } else { |
| | | if lockedSpaceFilterID == nil { |
| | | selectionBeforeSpaceFilter = view.effectiveSelectionForProjection() |
| | | lastSpaceFilterAction = "lock" |
| | | } else { |
| | | lastSpaceFilterAction = "switch" |
| | | } |
| | | lockedSpaceFilterID = spaceID |
| | | preferredSelection = nil |
| | | } |
| | | |
| | | let visibleViewModel = projectedViewModel( |
| | | from: sourceViewModel, |
| | | preferredSelection: preferredSelection |
| | | ) |
| | | currentViewModel = visibleViewModel |
| | | view.applyProjected(viewModel: visibleViewModel) |
| | | DevelopmentDiagnostics.log("quickSwitch.spaceFilter.apply", [ |
| | | "spaceID": spaceID, |
| | | "lockedSpaceID": lockedSpaceFilterID, |
| | | "action": lastSpaceFilterAction, |
| | | "visibleAppCount": visibleViewModel.appShelf.count, |
| | | "visibleWindowCount": visibleViewModel.windowCount |
| | | ]) |
| | | |
| | | if disableScreenshotRefresh { |
| | | view.markScreenshotsNotRequested(for: visibleViewModel, reason: "disabledByLaunchOption") |
| | | screenshotTask?.cancel() |
| | | screenshotTask = nil |
| | | } else { |
| | | scheduleScreenshotRefresh(for: visibleViewModel, generation: sessionGeneration) |
| | | } |
| | | |
| | | onSnapshotUpdated?() |
| | | } |
| | | |
| | |
| | | suppressedCloseTargets.append(target) |
| | | } |
| | | |
| | | guard let currentViewModel else { return } |
| | | let visibleViewModel = viewModel(currentViewModel, excluding: suppressedCloseTargets) |
| | | guard let sourceViewModel else { return } |
| | | let visibleViewModel = projectedViewModel(from: sourceViewModel, preferredSelection: view.effectiveSelectionForProjection()) |
| | | self.currentViewModel = visibleViewModel |
| | | view.apply(viewModel: visibleViewModel) |
| | | view.applyProjected(viewModel: visibleViewModel) |
| | | DevelopmentDiagnostics.log("quickSwitch.close.optimisticRemove", [ |
| | | "targetKind": request.kindDescription, |
| | | "appGroupIndex": request.appGroupIndex, |
| | |
| | | displays: viewModel.displays, |
| | | appShelf: appShelf, |
| | | waterfallColumns: columns, |
| | | initialSelection: initialSelection(in: columns, fallback: viewModel.initialSelection) |
| | | initialSelection: initialSelection(in: columns, fallback: viewModel.initialSelection), |
| | | lockedSpaceID: viewModel.lockedSpaceID |
| | | ) |
| | | } |
| | | |
| | | private func projectedViewModel( |
| | | from sourceViewModel: QuickSwitchViewModel, |
| | | preferredSelection: QuickSwitchSelection? = nil |
| | | ) -> QuickSwitchViewModel { |
| | | let closeProjectedViewModel = viewModel(sourceViewModel, excluding: suppressedCloseTargets) |
| | | return QuickSwitchSpaceFilterPolicy.projectedViewModel( |
| | | from: closeProjectedViewModel, |
| | | lockedSpaceID: lockedSpaceFilterID, |
| | | preferredSelection: preferredSelection |
| | | ) |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | private func window(for selection: QuickSwitchSelection) -> AlignerWindow? { |
| | | currentViewModel? |
| | | private func window( |
| | | for selection: QuickSwitchSelection, |
| | | in viewModel: QuickSwitchViewModel? |
| | | ) -> AlignerWindow? { |
| | | viewModel? |
| | | .waterfallColumns |
| | | .flatMap(\.windows) |
| | | .first { card in |
| | |
| | | switch result { |
| | | case .success(let success): |
| | | currentSnapshot = success.snapshot |
| | | let visibleViewModel = viewModel(success.viewModel, excluding: suppressedCloseTargets) |
| | | sourceViewModel = success.viewModel |
| | | let visibleViewModel = projectedViewModel( |
| | | from: success.viewModel, |
| | | preferredSelection: view.effectiveSelectionForProjection() |
| | | ) |
| | | currentViewModel = visibleViewModel |
| | | lastSnapshotError = nil |
| | | snapshotStartElapsedMilliseconds = success.timing.startElapsedMilliseconds |
| | |
| | | } |
| | | case .failure(let failure): |
| | | currentSnapshot = nil |
| | | sourceViewModel = nil |
| | | currentViewModel = nil |
| | | lastSnapshotError = failure.message |
| | | snapshotStartElapsedMilliseconds = failure.timing.startElapsedMilliseconds |
| | |
| | | let fixtureMultiDisplay: Bool |
| | | let fixtureCandidateFiltering: Bool |
| | | let fixtureActivation: Bool |
| | | let fixtureSpaceFilter: Bool |
| | | let debugHoveredAppGroupIndex: Int? |
| | | let debugOverlayWidth: CGFloat? |
| | | let debugKeySequence: [String] |
| | |
| | | fixtureMultiDisplay: arguments.contains("--round01-fixture-multi-display"), |
| | | fixtureCandidateFiltering: arguments.contains("--round01-fixture-candidate-filtering"), |
| | | fixtureActivation: arguments.contains("--round01-fixture-window-activation"), |
| | | fixtureSpaceFilter: arguments.contains("--round01-fixture-space-filter"), |
| | | debugHoveredAppGroupIndex: intValue(for: "--round01-debug-hover-app-index", in: arguments), |
| | | debugOverlayWidth: cgFloatValue(for: "--round01-debug-overlay-width", in: arguments), |
| | | debugKeySequence: stringListValue(for: "--round01-debug-key-sequence", in: arguments), |