|
@@ -0,0 +1,109 @@
|
|
|
|
|
+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<Bool>) {
|
|
|
|
|
+ _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)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+private struct FullScreenTopInsetModifier: ViewModifier {
|
|
|
|
|
+ @State private var isFullScreen = false
|
|
|
|
|
+
|
|
|
|
|
+ func body(content: Content) -> some View {
|
|
|
|
|
+ content
|
|
|
|
|
+ .padding(.top, isFullScreen ? AppWindow.fullScreenTopInset : 0)
|
|
|
|
|
+ .background {
|
|
|
|
|
+ WindowFullScreenObserver(isFullScreen: $isFullScreen)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+extension View {
|
|
|
|
|
+ func fullScreenTopInsetWhenNeeded() -> some View {
|
|
|
|
|
+ modifier(FullScreenTopInsetModifier())
|
|
|
|
|
+ }
|
|
|
|
|
+}
|