import SwiftUI import OnyxNotch import OnyxWidgetKit /// Der geteilte Zustand von Onyx: was im Panel liegt und auf welchen Displays. /// /// Änderungen wirken sofort. Eine Einstellung, die erst nach einem Neustart /// greift, ist in einer App ohne Fenster praktisch unauffindbar — man sieht ja /// nicht, dass etwas passiert ist. @MainActor @Observable final class AppModel { private let store: LayoutStore private let defaults: UserDefaults /// Wird gerufen, wenn sich die Panelgröße geändert haben könnte. var onLayoutChanged: (([WidgetPlacement]) -> Void)? /// Wird gerufen, wenn Panels auf anderen Displays erscheinen sollen. var onDisplayPolicyChanged: ((DisplayPolicy) -> Void)? var layout: [WidgetPlacement] { didSet { guard layout != oldValue else { return } persistLayout() onLayoutChanged?(layout) } } var displayPolicy: DisplayPolicy { didSet { guard displayPolicy != oldValue else { return } defaults.set(displayPolicy.rawValue, forKey: Keys.displayPolicy) onDisplayPolicyChanged?(displayPolicy) } } var showsMenuBarIcon: Bool { didSet { defaults.set(showsMenuBarIcon, forKey: Keys.showsMenuBarIcon) } } private enum Keys { static let displayPolicy = "onyx.displayPolicy" static let showsMenuBarIcon = "onyx.showsMenuBarIcon" } init(store: LayoutStore = .standard(), defaults: UserDefaults = .standard) { self.store = store self.defaults = defaults let known = Set(WidgetRegistry.shared.all.map(\.id)) self.layout = store.load(knownWidgetIDs: known) self.displayPolicy = defaults.string(forKey: Keys.displayPolicy) .flatMap(DisplayPolicy.init(rawValue:)) ?? .builtInOnly // Standardmäßig sichtbar: das Menüleistensymbol ist der einzige Weg // zurück, wenn die Notch einmal nicht reagiert. self.showsMenuBarIcon = defaults.object(forKey: Keys.showsMenuBarIcon) as? Bool ?? true } // MARK: - Widgets var panelContentSize: CGSize { WidgetGrid.panelSize(for: layout) } func isInPanel(_ widgetID: String) -> Bool { layout.contains { $0.widgetID == widgetID } } func add(_ widget: any OnyxWidget) { guard !isInPanel(widget.id) else { return } let size = widget.supportedSizes.first ?? .small layout.append(WidgetPlacement(widgetID: widget.id, size: size)) } func remove(_ widgetID: String) { layout.removeAll { $0.widgetID == widgetID } } func setSize(_ size: WidgetSize, for widgetID: String) { guard let index = layout.firstIndex(where: { $0.widgetID == widgetID }) else { return } layout[index].size = size } func move(fromOffsets source: IndexSet, toOffset destination: Int) { layout.move(fromOffsets: source, toOffset: destination) } private func persistLayout() { do { try store.save(layout) } catch { // Ein fehlgeschlagener Schreibvorgang darf die App nicht anhalten; // beim nächsten Start greift dann das Standardlayout. NSLog("Onyx: Layout konnte nicht gespeichert werden — \(error.localizedDescription)") } } }