import AppKit import QuartzCore import SwiftUI struct WindowFrameCalculator { static let horizontalPadding: CGFloat = 40 static let verticalPadding: CGFloat = 20 static func targetFrame( screenFrame: CGRect, currentWindowFrame: CGRect, notchState: NotchState, contentSize: CGSize, centerHorizontally: Bool ) -> CGRect { let windowWidth = contentSize.width + horizontalPadding let windowHeight = contentSize.height + verticalPadding let centeredX = screenFrame.origin.x + ((screenFrame.width - windowWidth) / 2) let x: CGFloat if centerHorizontally || notchState == .closed { x = centeredX } else { x = min( max(currentWindowFrame.minX, screenFrame.minX), screenFrame.maxX - windowWidth ) } return CGRect( x: x, y: screenFrame.origin.y + screenFrame.height - windowHeight, width: windowWidth, height: windowHeight ) } } @MainActor final class WindowCoordinator { private let focusRetryDelay: TimeInterval private let presetResizeFrameInterval: TimeInterval private let screenLookup: @MainActor (ScreenID) -> NSScreen? private let applicationActivator: @MainActor () -> Void private let hotkeyOpenStateHandler: @MainActor (Bool) -> Void private(set) var windows: [ScreenID: NotchWindow] = [:] private var presetResizeTimers: [ScreenID: Timer] = [:] init( focusRetryDelay: TimeInterval = 0.01, presetResizeFrameInterval: TimeInterval = 1.0 / 60.0, screenLookup: @escaping @MainActor (ScreenID) -> NSScreen? = { screenID in NSScreen.screens.first { $0.displayUUID == screenID } }, applicationActivator: @escaping @MainActor () -> Void = { NSApp.activate(ignoringOtherApps: true) }, hotkeyOpenStateHandler: @escaping @MainActor (Bool) -> Void = { isOpen in HotkeyManager.shared.isNotchOpen = isOpen } ) { self.focusRetryDelay = focusRetryDelay self.presetResizeFrameInterval = presetResizeFrameInterval self.screenLookup = screenLookup self.applicationActivator = applicationActivator self.hotkeyOpenStateHandler = hotkeyOpenStateHandler } func hasWindow(for screenID: ScreenID) -> Bool { windows[screenID] != nil } func windowScreenIDs() -> Set { Set(windows.keys) } func createWindow( on screen: NSScreen, context: ScreenContext, contentView: NSView, onResignKey: @escaping () -> Void ) { let initialFrame = WindowFrameCalculator.targetFrame( screenFrame: screen.frame, currentWindowFrame: .zero, notchState: context.notchState, contentSize: context.openNotchSize, centerHorizontally: true ) let window = NotchWindow( contentRect: initialFrame, styleMask: [.borderless, .nonactivatingPanel, .utilityWindow], backing: .buffered, defer: false ) window.onResignKey = onResignKey let containerView = NSView(frame: NSRect(origin: .zero, size: initialFrame.size)) containerView.autoresizesSubviews = true containerView.wantsLayer = true containerView.layer?.backgroundColor = NSColor.clear.cgColor contentView.frame = containerView.bounds contentView.autoresizingMask = [.width, .height] containerView.addSubview(contentView) window.contentView = containerView windows[context.id] = window updateWindowFrame(for: context.id, context: context, centerHorizontally: true) window.orderFrontRegardless() } func repositionWindow(for screenID: ScreenID, context: ScreenContext, centerHorizontally: Bool = false) { updateWindowFrame(for: screenID, context: context, centerHorizontally: centerHorizontally) } func updateWindowFrame( for screenID: ScreenID, context: ScreenContext, contentSize: CGSize? = nil, centerHorizontally: Bool = false ) { guard let screen = screenLookup(screenID), let window = windows[screenID] else { return } let frame = WindowFrameCalculator.targetFrame( screenFrame: screen.frame, currentWindowFrame: window.frame, notchState: context.notchState, contentSize: resolvedContentSize(for: context, override: contentSize), centerHorizontally: centerHorizontally ) guard !window.frame.equalTo(frame) else { return } window.setFrame(frame, display: false) } func animatePresetResize( for screenID: ScreenID, context: ScreenContext, from startSize: CGSize, to targetSize: CGSize, duration: TimeInterval ) { cancelPresetResize(for: screenID) guard startSize != targetSize else { context.notchSize = targetSize updateWindowFrame(for: screenID, context: context, contentSize: targetSize, centerHorizontally: true) return } context.isPresetResizing = true let startTime = CACurrentMediaTime() let frameInterval = max(duration, presetResizeFrameInterval) let timer = Timer(timeInterval: presetResizeFrameInterval, repeats: true) { [weak self] timer in MainActor.assumeIsolated { guard let self else { timer.invalidate() return } let elapsed = CACurrentMediaTime() - startTime let progress = min(1, elapsed / frameInterval) let easedProgress = 0.5 - (cos(.pi * progress) / 2) let size = CGSize( width: startSize.width + ((targetSize.width - startSize.width) * easedProgress), height: startSize.height + ((targetSize.height - startSize.height) * easedProgress) ) context.notchSize = size self.updateWindowFrame(for: screenID, context: context, contentSize: size, centerHorizontally: true) if progress >= 1 { context.notchSize = targetSize context.isPresetResizing = false self.updateWindowFrame(for: screenID, context: context, contentSize: targetSize, centerHorizontally: true) self.presetResizeTimers[screenID] = nil timer.invalidate() } } } presetResizeTimers[screenID] = timer RunLoop.main.add(timer, forMode: .common) timer.fire() } func presentOpen( for screenID: ScreenID, terminalViewProvider: @escaping @MainActor () -> NSView? ) { guard let window = windows[screenID] else { return } window.isNotchOpen = true updateHotkeyOpenState() applicationActivator() window.makeKeyAndOrderFront(nil) focusActiveTerminal( in: screenID, attemptsRemaining: 12, terminalViewProvider: terminalViewProvider ) } func focusActiveTerminal( for screenID: ScreenID, terminalViewProvider: @escaping @MainActor () -> NSView? ) { focusActiveTerminal( in: screenID, attemptsRemaining: 12, terminalViewProvider: terminalViewProvider ) } func presentClose(for screenID: ScreenID) { guard let window = windows[screenID] else { return } window.isNotchOpen = false updateHotkeyOpenState() } func cleanupAllWindows() { for timer in presetResizeTimers.values { timer.invalidate() } presetResizeTimers.removeAll() for window in windows.values { window.orderOut(nil) window.close() } windows.removeAll() updateHotkeyOpenState() } private func focusActiveTerminal( in screenID: ScreenID, attemptsRemaining: Int, terminalViewProvider: @escaping @MainActor () -> NSView? ) { guard let window = windows[screenID], let terminalView = terminalViewProvider() else { return } if terminalView.window === window { window.makeFirstResponder(terminalView) return } guard attemptsRemaining > 0 else { return } DispatchQueue.main.asyncAfter(deadline: .now() + focusRetryDelay) { [weak self] in Task { @MainActor in self?.focusActiveTerminal( in: screenID, attemptsRemaining: attemptsRemaining - 1, terminalViewProvider: terminalViewProvider ) } } } private func cancelPresetResize(for screenID: ScreenID) { presetResizeTimers[screenID]?.invalidate() presetResizeTimers[screenID] = nil } private func resolvedContentSize(for context: ScreenContext, override: CGSize?) -> CGSize { if let override { return override } return context.notchState == .open ? context.notchSize : context.openNotchSize } private func updateHotkeyOpenState() { hotkeyOpenStateHandler(windows.values.contains(where: \.isNotchOpen)) } }