WindowFullScreenObserver.swift 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. import AppKit
  2. import SwiftUI
  3. struct WindowFullScreenObserver: NSViewRepresentable {
  4. @Binding var isFullScreen: Bool
  5. func makeCoordinator() -> Coordinator {
  6. Coordinator(isFullScreen: $isFullScreen)
  7. }
  8. func makeNSView(context: Context) -> WindowObservationView {
  9. let view = WindowObservationView()
  10. view.onWindowChange = { [weak coordinator = context.coordinator] window in
  11. coordinator?.observe(window: window)
  12. }
  13. return view
  14. }
  15. func updateNSView(_ nsView: WindowObservationView, context: Context) {
  16. context.coordinator.observe(window: nsView.window)
  17. }
  18. final class Coordinator {
  19. @Binding private var isFullScreen: Bool
  20. private var observers: [NSObjectProtocol] = []
  21. private weak var observedWindow: NSWindow?
  22. init(isFullScreen: Binding<Bool>) {
  23. _isFullScreen = isFullScreen
  24. }
  25. func observe(window: NSWindow?) {
  26. guard window !== observedWindow else {
  27. if let window {
  28. updateFullScreenState(for: window)
  29. }
  30. return
  31. }
  32. removeObservers()
  33. observedWindow = window
  34. guard let window else {
  35. isFullScreen = false
  36. return
  37. }
  38. updateFullScreenState(for: window)
  39. let center = NotificationCenter.default
  40. observers = [
  41. center.addObserver(
  42. forName: NSWindow.didEnterFullScreenNotification,
  43. object: window,
  44. queue: .main
  45. ) { [weak self] _ in
  46. self?.updateFullScreenState(for: window)
  47. },
  48. center.addObserver(
  49. forName: NSWindow.didExitFullScreenNotification,
  50. object: window,
  51. queue: .main
  52. ) { [weak self] _ in
  53. self?.updateFullScreenState(for: window)
  54. },
  55. ]
  56. }
  57. private func updateFullScreenState(for window: NSWindow) {
  58. isFullScreen = window.styleMask.contains(.fullScreen)
  59. }
  60. private func removeObservers() {
  61. observers.forEach { NotificationCenter.default.removeObserver($0) }
  62. observers.removeAll()
  63. }
  64. deinit {
  65. removeObservers()
  66. }
  67. }
  68. }
  69. final class WindowObservationView: NSView {
  70. var onWindowChange: ((NSWindow?) -> Void)?
  71. override func viewDidMoveToWindow() {
  72. super.viewDidMoveToWindow()
  73. onWindowChange?(window)
  74. }
  75. }
  76. private struct FullScreenTopInsetModifier: ViewModifier {
  77. @State private var isFullScreen = false
  78. func body(content: Content) -> some View {
  79. content
  80. .padding(.top, isFullScreen ? AppWindow.fullScreenTopInset : 0)
  81. .background {
  82. WindowFullScreenObserver(isFullScreen: $isFullScreen)
  83. }
  84. }
  85. }
  86. extension View {
  87. func fullScreenTopInsetWhenNeeded() -> some View {
  88. modifier(FullScreenTopInsetModifier())
  89. }
  90. }