import AppKit import SwiftUI struct WindowFullScreenObserver: NSViewRepresentable { @Binding var isFullScreen: Bool func makeCoordinator() -> Coordinator { Coordinator(isFullScreen: $isFullScreen) } func makeNSView(context: Context) -> WindowObservationView { let view = WindowObservationView() view.onWindowChange = { [weak coordinator = context.coordinator] window in coordinator?.observe(window: window) } return view } func updateNSView(_ nsView: WindowObservationView, context: Context) { context.coordinator.observe(window: nsView.window) } final class Coordinator { @Binding private var isFullScreen: Bool private var observers: [NSObjectProtocol] = [] private weak var observedWindow: NSWindow? init(isFullScreen: Binding) { _isFullScreen = isFullScreen } func observe(window: NSWindow?) { guard window !== observedWindow else { if let window { updateFullScreenState(for: window) } return } removeObservers() observedWindow = window guard let window else { isFullScreen = false return } updateFullScreenState(for: window) let center = NotificationCenter.default observers = [ center.addObserver( forName: NSWindow.didEnterFullScreenNotification, object: window, queue: .main ) { [weak self] _ in self?.updateFullScreenState(for: window) }, center.addObserver( forName: NSWindow.didExitFullScreenNotification, object: window, queue: .main ) { [weak self] _ in self?.updateFullScreenState(for: window) }, ] } private func updateFullScreenState(for window: NSWindow) { isFullScreen = window.styleMask.contains(.fullScreen) } private func removeObservers() { observers.forEach { NotificationCenter.default.removeObserver($0) } observers.removeAll() } deinit { removeObservers() } } } final class WindowObservationView: NSView { var onWindowChange: ((NSWindow?) -> Void)? override func viewDidMoveToWindow() { super.viewDidMoveToWindow() onWindowChange?(window) } override func hitTest(_ point: NSPoint) -> NSView? { nil } } private struct FullScreenLayoutModifier: ViewModifier { @State private var isFullScreen = false func body(content: Content) -> some View { VStack(spacing: 0) { if isFullScreen { AppTheme.sidebarBackground .frame(height: AppWindow.fullScreenTopInset) .frame(maxWidth: .infinity) .allowsHitTesting(false) } content .frame(maxWidth: .infinity, maxHeight: .infinity) } .frame(maxWidth: .infinity, maxHeight: .infinity) .background { WindowFullScreenObserver(isFullScreen: $isFullScreen) } } } extension View { func fullScreenLayoutWhenNeeded() -> some View { modifier(FullScreenLayoutModifier()) } }