Yep. AI rewrote the whole thing.
This commit is contained in:
189
Downterm/CommandNotch/Models/NotchOrchestrator.swift
Normal file
189
Downterm/CommandNotch/Models/NotchOrchestrator.swift
Normal file
@@ -0,0 +1,189 @@
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
protocol ScreenRegistryType: AnyObject {
|
||||
func allScreens() -> [ScreenContext]
|
||||
func screenContext(for id: ScreenID) -> ScreenContext?
|
||||
func activeScreenID() -> ScreenID?
|
||||
func presentingScreenID(for workspaceID: WorkspaceID) -> ScreenID?
|
||||
@discardableResult
|
||||
func claimWorkspacePresentation(for screenID: ScreenID) -> ScreenID?
|
||||
func releaseWorkspacePresentation(for screenID: ScreenID)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
protocol NotchPresentationHost: AnyObject {
|
||||
func canPresentNotch(for screenID: ScreenID) -> Bool
|
||||
func performOpenPresentation(for screenID: ScreenID)
|
||||
func performClosePresentation(for screenID: ScreenID)
|
||||
}
|
||||
|
||||
protocol SchedulerType {
|
||||
@MainActor
|
||||
func schedule(after interval: TimeInterval, action: @escaping @MainActor () -> Void) -> AnyCancellable
|
||||
}
|
||||
|
||||
struct TaskScheduler: SchedulerType {
|
||||
@MainActor
|
||||
func schedule(after interval: TimeInterval, action: @escaping @MainActor () -> Void) -> AnyCancellable {
|
||||
let task = Task { @MainActor in
|
||||
try? await Task.sleep(nanoseconds: UInt64(interval * 1_000_000_000))
|
||||
guard !Task.isCancelled else { return }
|
||||
action()
|
||||
}
|
||||
|
||||
return AnyCancellable {
|
||||
task.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class NotchOrchestrator {
|
||||
private let screenRegistry: any ScreenRegistryType
|
||||
private weak var host: (any NotchPresentationHost)?
|
||||
private let settingsController: AppSettingsController
|
||||
private let scheduler: any SchedulerType
|
||||
|
||||
private var hoverOpenTasks: [ScreenID: AnyCancellable] = [:]
|
||||
private var closeTransitionTasks: [ScreenID: AnyCancellable] = [:]
|
||||
|
||||
init(
|
||||
screenRegistry: any ScreenRegistryType,
|
||||
host: any NotchPresentationHost,
|
||||
settingsController: AppSettingsController? = nil,
|
||||
scheduler: (any SchedulerType)? = nil
|
||||
) {
|
||||
self.screenRegistry = screenRegistry
|
||||
self.host = host
|
||||
self.settingsController = settingsController ?? AppSettingsController.shared
|
||||
self.scheduler = scheduler ?? TaskScheduler()
|
||||
}
|
||||
|
||||
func toggleOnActiveScreen() {
|
||||
guard let screenID = screenRegistry.activeScreenID(),
|
||||
host?.canPresentNotch(for: screenID) == true,
|
||||
let context = screenRegistry.screenContext(for: screenID) else {
|
||||
return
|
||||
}
|
||||
|
||||
if context.notchState == .open {
|
||||
close(screenID: screenID)
|
||||
} else {
|
||||
open(screenID: screenID)
|
||||
}
|
||||
}
|
||||
|
||||
func open(screenID: ScreenID) {
|
||||
guard host?.canPresentNotch(for: screenID) == true,
|
||||
let context = screenRegistry.screenContext(for: screenID) else {
|
||||
return
|
||||
}
|
||||
|
||||
if let presentingScreenID = screenRegistry.presentingScreenID(for: context.workspaceID),
|
||||
presentingScreenID != screenID {
|
||||
close(screenID: presentingScreenID)
|
||||
}
|
||||
|
||||
cancelHoverOpen(for: screenID)
|
||||
cancelCloseTransition(for: screenID)
|
||||
context.cancelCloseTransition()
|
||||
|
||||
withAnimation(context.openAnimation) {
|
||||
context.open()
|
||||
}
|
||||
|
||||
_ = screenRegistry.claimWorkspacePresentation(for: screenID)
|
||||
host?.performOpenPresentation(for: screenID)
|
||||
}
|
||||
|
||||
func close(screenID: ScreenID) {
|
||||
guard let context = screenRegistry.screenContext(for: screenID) else { return }
|
||||
|
||||
cancelHoverOpen(for: screenID)
|
||||
cancelCloseTransition(for: screenID)
|
||||
context.beginCloseTransition()
|
||||
|
||||
closeTransitionTasks[screenID] = scheduler.schedule(after: context.closeInteractionLockDuration) { [weak self] in
|
||||
self?.finishCloseTransition(for: screenID)
|
||||
}
|
||||
|
||||
withAnimation(context.closeAnimation) {
|
||||
context.close()
|
||||
}
|
||||
|
||||
screenRegistry.releaseWorkspacePresentation(for: screenID)
|
||||
host?.performClosePresentation(for: screenID)
|
||||
}
|
||||
|
||||
func handleHoverChange(_ hovering: Bool, for screenID: ScreenID) {
|
||||
guard let context = screenRegistry.screenContext(for: screenID) else { return }
|
||||
|
||||
context.isHovering = hovering
|
||||
|
||||
if hovering {
|
||||
scheduleHoverOpenIfNeeded(for: screenID)
|
||||
} else {
|
||||
cancelHoverOpen(for: screenID)
|
||||
context.clearHoverOpenSuppression()
|
||||
}
|
||||
}
|
||||
|
||||
func cancelAllPendingWork() {
|
||||
for task in hoverOpenTasks.values {
|
||||
task.cancel()
|
||||
}
|
||||
for task in closeTransitionTasks.values {
|
||||
task.cancel()
|
||||
}
|
||||
|
||||
hoverOpenTasks.removeAll()
|
||||
closeTransitionTasks.removeAll()
|
||||
}
|
||||
|
||||
private func scheduleHoverOpenIfNeeded(for screenID: ScreenID) {
|
||||
cancelHoverOpen(for: screenID)
|
||||
|
||||
guard let context = screenRegistry.screenContext(for: screenID) else { return }
|
||||
guard settingsController.settings.behavior.openNotchOnHover,
|
||||
context.notchState == .closed,
|
||||
!context.isCloseTransitionActive,
|
||||
!context.suppressHoverOpenUntilHoverExit,
|
||||
context.isHovering else {
|
||||
return
|
||||
}
|
||||
|
||||
hoverOpenTasks[screenID] = scheduler.schedule(after: settingsController.settings.behavior.minimumHoverDuration) { [weak self] in
|
||||
guard let self,
|
||||
let context = self.screenRegistry.screenContext(for: screenID),
|
||||
context.isHovering,
|
||||
context.notchState == .closed,
|
||||
!context.isCloseTransitionActive,
|
||||
!context.suppressHoverOpenUntilHoverExit else {
|
||||
return
|
||||
}
|
||||
|
||||
self.hoverOpenTasks[screenID] = nil
|
||||
self.open(screenID: screenID)
|
||||
}
|
||||
}
|
||||
|
||||
private func finishCloseTransition(for screenID: ScreenID) {
|
||||
closeTransitionTasks[screenID] = nil
|
||||
guard let context = screenRegistry.screenContext(for: screenID) else { return }
|
||||
|
||||
context.endCloseTransition()
|
||||
scheduleHoverOpenIfNeeded(for: screenID)
|
||||
}
|
||||
|
||||
private func cancelHoverOpen(for screenID: ScreenID) {
|
||||
hoverOpenTasks[screenID]?.cancel()
|
||||
hoverOpenTasks[screenID] = nil
|
||||
}
|
||||
|
||||
private func cancelCloseTransition(for screenID: ScreenID) {
|
||||
closeTransitionTasks[screenID]?.cancel()
|
||||
closeTransitionTasks[screenID] = nil
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user