import AppKit
|
import CoreGraphics
|
import AlignerCore
|
|
@MainActor
|
final class WindowCoordinator: OverlaySessionDelegate {
|
private let session: OverlaySession
|
private let overlayWindowController: OverlayWindowController
|
private let screenProvider: () -> [NSScreen]
|
private let mouseLocationProvider: () -> NSPoint
|
private let screenSpaceStateProvider: any OverlayScreenSpaceStateProviding
|
private var currentContext: OverlayContext?
|
|
init(
|
overlayWindowController: OverlayWindowController,
|
screenProvider: @escaping () -> [NSScreen] = { NSScreen.screens },
|
mouseLocationProvider: @escaping () -> NSPoint = { NSEvent.mouseLocation },
|
screenSpaceStateProvider: any OverlayScreenSpaceStateProviding = SkyLightOverlayScreenSpaceStateProvider()
|
) {
|
self.session = OverlaySession()
|
self.overlayWindowController = overlayWindowController
|
self.screenProvider = screenProvider
|
self.mouseLocationProvider = mouseLocationProvider
|
self.screenSpaceStateProvider = screenSpaceStateProvider
|
self.session.delegate = self
|
}
|
|
convenience init(
|
contentView: NSView? = nil,
|
title: String = "Aligner Quick Switch Overlay",
|
debugOverlayWidth: CGFloat? = nil
|
) {
|
let host = QuickSwitchOverlayHost(
|
title: title,
|
contentView: contentView,
|
debugOverlayWidth: debugOverlayWidth
|
)
|
self.init(overlayWindowController: OverlayWindowController(host: host))
|
}
|
|
var isQuickSwitchVisible: Bool {
|
session.state != .idle && overlayWindowController.isVisible
|
}
|
|
var currentLevel: CGWindowLevel {
|
overlayWindowController.currentLevel
|
}
|
|
func toggleQuickSwitch() {
|
if isQuickSwitchVisible {
|
closeQuickSwitch(reason: .userClosed)
|
} else {
|
openQuickSwitch()
|
}
|
}
|
|
func openQuickSwitch() {
|
let placement = OverlayPlacementPolicy.placement(
|
forMouseLocation: mouseLocationProvider(),
|
screens: screenRecords()
|
)
|
let context = placement.overlayContext
|
|
currentContext = context
|
session.openQuickSwitch(context: context)
|
overlayWindowController.show(context: context)
|
}
|
|
func closeQuickSwitch(reason: DismissReason) {
|
session.dismiss(reason: reason)
|
}
|
|
func promoteForTextInput() {
|
let allowAppActivation = currentContext?.spacePolicy.avoidSpaceSwitch != true
|
overlayWindowController.promoteForTextInput(allowAppActivation: allowAppActivation)
|
}
|
|
func sessionDidChangeState(_ state: SessionState) {}
|
|
func sessionShouldDismiss(reason: DismissReason) {
|
overlayWindowController.hide(reason: reason)
|
currentContext = nil
|
}
|
|
func sessionDidTimeout() {}
|
|
private func screenRecords() -> [OverlayScreenRecord] {
|
screenProvider().compactMap { screen in
|
guard let displayUUID = Self.displayUUID(for: screen) else { return nil }
|
return OverlayScreenRecord(
|
displayUUID: displayUUID,
|
frame: screen.frame,
|
visibleFrame: screen.visibleFrame,
|
spaceState: screenSpaceStateProvider.state(forDisplayUUID: displayUUID)
|
)
|
}
|
}
|
|
private static func displayUUID(for screen: NSScreen) -> String? {
|
guard
|
let number = screen.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? NSNumber,
|
let unmanagedUUID = CGDisplayCreateUUIDFromDisplayID(CGDirectDisplayID(number.uint32Value))
|
else {
|
return nil
|
}
|
|
let uuid = unmanagedUUID.takeRetainedValue()
|
return CFUUIDCreateString(nil, uuid) as String?
|
}
|
}
|