79 lines
2.3 KiB
Swift
79 lines
2.3 KiB
Swift
import AppKit
|
|
import SwiftUI
|
|
|
|
/// Borderless, floating NSPanel that hosts the notch overlay.
|
|
/// When the notch is open the window accepts key status so the
|
|
/// terminal can receive keyboard input. On resignKey the
|
|
/// `onResignKey` closure fires to close the notch.
|
|
class NotchWindow: NSPanel {
|
|
|
|
var isNotchOpen: Bool = false
|
|
|
|
/// Called when the window loses key status while the notch is open.
|
|
var onResignKey: (() -> Void)?
|
|
|
|
override init(
|
|
contentRect: NSRect,
|
|
styleMask style: NSWindow.StyleMask,
|
|
backing backingStoreType: NSWindow.BackingStoreType,
|
|
defer flag: Bool
|
|
) {
|
|
// Start as a plain borderless utility panel.
|
|
// .nonactivatingPanel is NOT included so the window can
|
|
// properly accept key status when the notch opens.
|
|
super.init(
|
|
contentRect: contentRect,
|
|
styleMask: [.borderless, .utilityWindow, .nonactivatingPanel],
|
|
backing: .buffered,
|
|
defer: flag
|
|
)
|
|
configureWindow()
|
|
}
|
|
|
|
private func configureWindow() {
|
|
isOpaque = false
|
|
backgroundColor = .clear
|
|
|
|
isFloatingPanel = true
|
|
level = .mainMenu + 3
|
|
|
|
titleVisibility = .hidden
|
|
titlebarAppearsTransparent = true
|
|
hasShadow = false
|
|
|
|
isMovable = false
|
|
isMovableByWindowBackground = false
|
|
|
|
collectionBehavior = [
|
|
.canJoinAllSpaces,
|
|
.stationary,
|
|
.fullScreenAuxiliary,
|
|
.ignoresCycle
|
|
]
|
|
|
|
appearance = NSAppearance(named: .darkAqua)
|
|
|
|
// Accepts mouse events when the app is NOT active so the
|
|
// user can click the closed notch to open it.
|
|
acceptsMouseMovedEvents = true
|
|
}
|
|
|
|
// MARK: - Key window management
|
|
|
|
override var canBecomeKey: Bool { isNotchOpen }
|
|
override var canBecomeMain: Bool { false }
|
|
|
|
override func resignKey() {
|
|
super.resignKey()
|
|
if isNotchOpen {
|
|
// Brief async dispatch so the new key window settles first —
|
|
// avoids closing when we're just transferring focus between
|
|
// our own windows (e.g. opening settings).
|
|
DispatchQueue.main.async { [weak self] in
|
|
guard let self, self.isNotchOpen else { return }
|
|
self.onResignKey?()
|
|
}
|
|
}
|
|
}
|
|
}
|