Yep. AI rewrote the whole thing.
This commit is contained in:
291
Downterm/CommandNotch/Managers/WindowCoordinator.swift
Normal file
291
Downterm/CommandNotch/Managers/WindowCoordinator.swift
Normal file
@@ -0,0 +1,291 @@
|
||||
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<ScreenID> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user