|
|
@@ -0,0 +1,106 @@
|
|
|
+import AppKit
|
|
|
+import SwiftUI
|
|
|
+
|
|
|
+struct WindowChromeConfigurator: ViewModifier {
|
|
|
+ func body(content: Content) -> some View {
|
|
|
+ content.background(WindowChromeAccessor())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+private struct WindowChromeAccessor: NSViewRepresentable {
|
|
|
+ func makeCoordinator() -> Coordinator {
|
|
|
+ Coordinator()
|
|
|
+ }
|
|
|
+
|
|
|
+ func makeNSView(context: Context) -> NSView {
|
|
|
+ let view = NSView(frame: .zero)
|
|
|
+ DispatchQueue.main.async {
|
|
|
+ context.coordinator.configure(window: view.window)
|
|
|
+ }
|
|
|
+ return view
|
|
|
+ }
|
|
|
+
|
|
|
+ func updateNSView(_ nsView: NSView, context: Context) {
|
|
|
+ DispatchQueue.main.async {
|
|
|
+ context.coordinator.configure(window: nsView.window)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ final class Coordinator {
|
|
|
+ private weak var observedWindow: NSWindow?
|
|
|
+ private var observers: [NSObjectProtocol] = []
|
|
|
+
|
|
|
+ func configure(window: NSWindow?) {
|
|
|
+ guard let window else { return }
|
|
|
+
|
|
|
+ if window !== observedWindow {
|
|
|
+ teardown()
|
|
|
+ observedWindow = window
|
|
|
+ setupObservers(for: window)
|
|
|
+ }
|
|
|
+
|
|
|
+ applyChrome(to: window)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func applyChrome(to window: NSWindow) {
|
|
|
+ window.styleMask.insert(.fullSizeContentView)
|
|
|
+ window.titlebarAppearsTransparent = true
|
|
|
+ window.titleVisibility = .hidden
|
|
|
+ window.isMovableByWindowBackground = true
|
|
|
+ window.titlebarSeparatorStyle = .none
|
|
|
+ window.title = ""
|
|
|
+ window.appearance = NSAppearance(named: .aqua)
|
|
|
+ window.backgroundColor = NSColor(AppTheme.background)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func setupObservers(for window: NSWindow) {
|
|
|
+ let center = NotificationCenter.default
|
|
|
+ let names: [Notification.Name] = [
|
|
|
+ NSWindow.didBecomeKeyNotification,
|
|
|
+ NSWindow.didEnterFullScreenNotification,
|
|
|
+ NSWindow.willExitFullScreenNotification,
|
|
|
+ NSWindow.didExitFullScreenNotification,
|
|
|
+ NSWindow.didResizeNotification,
|
|
|
+ ]
|
|
|
+
|
|
|
+ for name in names {
|
|
|
+ let observer = center.addObserver(
|
|
|
+ forName: name,
|
|
|
+ object: window,
|
|
|
+ queue: .main
|
|
|
+ ) { [weak self] notification in
|
|
|
+ guard let self,
|
|
|
+ let window = notification.object as? NSWindow else { return }
|
|
|
+ self.applyChrome(to: window)
|
|
|
+ }
|
|
|
+ observers.append(observer)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private func teardown() {
|
|
|
+ let center = NotificationCenter.default
|
|
|
+ observers.forEach { center.removeObserver($0) }
|
|
|
+ observers.removeAll()
|
|
|
+ }
|
|
|
+
|
|
|
+ deinit {
|
|
|
+ teardown()
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+extension View {
|
|
|
+ func configureWindowChrome() -> some View {
|
|
|
+ modifier(WindowChromeConfigurator())
|
|
|
+ }
|
|
|
+
|
|
|
+ @ViewBuilder
|
|
|
+ func lightWindowToolbarBackground() -> some View {
|
|
|
+ if #available(macOS 13.0, *) {
|
|
|
+ toolbarBackground(AppTheme.background, for: .windowToolbar)
|
|
|
+ .toolbarBackground(.visible, for: .windowToolbar)
|
|
|
+ } else {
|
|
|
+ self
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|