import AppKit import SwiftUI import CalendarProvider import WeatherProvider import MetricsProvider /// Führt das Einstellungsfenster selbst. /// /// SwiftUIs `Settings`-Szene wird über `NSApp.sendAction(showSettingsWindow:)` /// geöffnet — ein privater Selektor, dessen Name sich zwischen macOS-Versionen /// schon geändert hat (`showPreferencesWindow:` davor) und der bei /// `.accessory`-Apps ohne Menüleiste unzuverlässig ankommt. Genau das ist hier /// passiert: der Menüeintrag tat nichts. /// /// Ein eigenes `NSWindow` hat diese Abhängigkeit nicht. Es kostet ein paar /// Zeilen mehr und funktioniert dafür. @MainActor final class SettingsWindowController: NSObject, NSWindowDelegate { private var window: NSWindow? /// Muss außerhalb der View liegen, damit ein erneutes Öffnen den Reiter setzen kann. private var selectedTab: SettingsTab = .widgets /// Ob Onyx dauerhaft im Dock steht. Bestimmt, wohin nach dem Schließen /// zurückgeschaltet wird. var keepsDockIcon = false func show(model: AppModel, calendarModel: CalendarModel, weatherModel: WeatherModel, metricsModel: MetricsModel?, fanControl: FanControl, tab: SettingsTab = .widgets) { selectedTab = tab keepsDockIcon = model.showsDockIcon // Solange ein Fenster offen ist, gehört Onyx in den Dock. // // Als reine `.accessory`-App hat sie dort keinen Eintrag — das ist im // Hintergrund richtig, aber mit sichtbarem Fenster verwirrend: das // Fenster lässt sich dann weder über den Dock noch über ⌘-Tab // zurückholen, wenn es hinter etwas anderes rutscht. NSApp.setActivationPolicy(.regular) if window == nil { let hosting = NSHostingController( rootView: SettingsView(model: model, calendarModel: calendarModel, weatherModel: weatherModel, metricsModel: metricsModel, fanControl: fanControl, selectedTab: Binding( get: { [weak self] in self?.selectedTab ?? .widgets }, set: { [weak self] in self?.selectedTab = $0 }))) let window = NSWindow(contentViewController: hosting) window.title = "Onyx" window.styleMask = [.titled, .closable, .miniaturizable] window.isReleasedWhenClosed = false window.delegate = self window.center() // Über die Position freuen sich Nutzer mit mehreren Displays: // beim zweiten Öffnen steht es wieder da, wo sie es hingeschoben haben. window.setFrameAutosaveName("onyx.settings") self.window = window } // Ohne `activate` bleibt das Fenster einer .accessory-App hinter der // aktiven App liegen und wirkt, als wäre nichts passiert. NSApp.activate(ignoringOtherApps: true) window?.makeKeyAndOrderFront(nil) } func windowWillClose(_ notification: Notification) { // Zurück in den Hintergrund — es sei denn, der Dock-Eintrag ist // ausdrücklich gewünscht. if !keepsDockIcon { NSApp.setActivationPolicy(.accessory) } NSApp.hide(nil) } }