diff --git a/Onyx/AppModel.swift b/Onyx/AppModel.swift index c188001..8065df4 100644 --- a/Onyx/AppModel.swift +++ b/Onyx/AppModel.swift @@ -124,19 +124,38 @@ final class AppModel { var onTriggerAreaChanged: ((TriggerArea) -> Void)? - var panelContentSize: CGSize { - WidgetGrid.panelSize(for: layout, availableWidth: Self.availableWidth) + /// Zählt Bildschirmwechsel mit. + /// + /// Nur dafür da, beobachtbar zu sein: die Breite kommt aus `NSScreen`, und + /// davon erfährt SwiftUI von sich aus nichts. Ohne diesen Zähler bliebe ein + /// Panel, das einmal für einen schmalen Fernseher gerechnet wurde, für + /// immer schmal — auch nach dem Abstecken. + private(set) var screenGeneration = 0 + + func noteScreensChanged() { screenGeneration += 1 } + + /// Wie breit das Panel auf **diesem** Bildschirm werden darf. + /// + /// Je Bildschirm und nicht für alle zusammen. Vorher gab der schmalste + /// aktive Bildschirm den Ausschlag — mit der Begründung, dasselbe Panel + /// erscheine überall. Der Fall, der das widerlegt: ein Fernseher mit 1280 + /// Punkten Breite drückte das Panel auch auf dem Notebook zusammen, die + /// Kacheln brachen um, und es wurde hoch statt breit. Ein Panel, das auf + /// dem eigenen Bildschirm nicht mehr passt, weil woanders ein Gerät hängt, + /// ist der schlechtere Kompromiss. + /// + /// Der Abzug lässt Luft zum Rand — ein Panel, das exakt bis zur Kante + /// geht, wirkt eingeklemmt. + func availableWidth(forDisplay id: CGDirectDisplayID) -> CGFloat { + _ = screenGeneration + let width = NSScreen.screens.first { $0.displayID == id }?.frame.width + ?? NSScreen.screens.map(\.frame.width).min() + ?? 1440 + return width - 120 } - /// Wie breit das Panel höchstens werden darf. - /// - /// Der **schmalste** aktive Bildschirm gibt den Ausschlag: dasselbe Panel - /// erscheint auf allen Displays, und eines, das auf dem Notebook über den - /// Rand ragt, wäre auf dem großen Monitor kein Trost. Der Abzug lässt Luft - /// zum Rand — ein Panel, das exakt bis zur Kante geht, wirkt eingeklemmt. - static var availableWidth: CGFloat { - let narrowest = NSScreen.screens.map(\.frame.width).min() ?? 1440 - return narrowest - 120 + func panelContentSize(forDisplay id: CGDirectDisplayID) -> CGSize { + WidgetGrid.panelSize(for: layout, availableWidth: availableWidth(forDisplay: id)) } func isInPanel(_ widgetID: String) -> Bool { diff --git a/Onyx/OnyxApp.swift b/Onyx/OnyxApp.swift index f477a12..3d3ad55 100644 --- a/Onyx/OnyxApp.swift +++ b/Onyx/OnyxApp.swift @@ -72,15 +72,19 @@ final class AppDelegate: NSObject, NSApplicationDelegate { NSApp.setActivationPolicy(model.showsDockIcon ? .regular : .accessory) let coordinator = NotchCoordinator(policy: model.displayPolicy, - trigger: model.triggerArea) { _, presentation in + trigger: model.triggerArea) { displayID, presentation in NotchHostingView( rootView: NotchContainer(presentation: presentation) { - NotchPanelContent(model: model) + NotchPanelContent(model: model, displayID: displayID) }, presentation: presentation) } coordinator.start() self.coordinator = coordinator + + NotificationCenter.default.addObserver( + self, selector: #selector(screensChanged), + name: NSApplication.didChangeScreenParametersNotification, object: nil) applyContentSize() // Einstellungen wirken sofort. Eine Änderung, die erst nach einem @@ -244,10 +248,26 @@ final class AppDelegate: NSObject, NSApplicationDelegate { WidgetRegistry.shared.register(ShelfWidget(store: shelfStore)) } + /// Je Bildschirm eine eigene Größe. + /// + /// Ein Fernseher mit 1280 Punkten Breite darf das Panel auf dem Notebook + /// nicht zusammendrücken. private func applyContentSize() { guard let model, let coordinator else { return } - let size = model.panelContentSize - coordinator.activeControllers.forEach { $0.contentSize = size } + for controller in coordinator.activeControllers { + controller.contentSize = model.panelContentSize(forDisplay: controller.displayID) + } + } + + /// Bildschirm angesteckt, abgezogen oder umgestellt. + /// + /// Ohne das blieb die einmal gerechnete Größe stehen — auch nachdem das + /// Gerät wieder weg war. Verzögert um einen Durchlauf, damit der Koordinator + /// seine Fenster vorher neu aufgebaut hat: sonst bekämen die alten die neue + /// Größe und die neuen gar keine. + @objc private func screensChanged() { + model?.noteScreensChanged() + Task { @MainActor [weak self] in self?.applyContentSize() } } // MARK: - Menüleiste @@ -361,9 +381,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate { /// sobald in den Einstellungen etwas geändert wird. private struct NotchPanelContent: View { let model: AppModel + let displayID: CGDirectDisplayID var body: some View { - WidgetGrid(placements: model.layout, availableWidth: AppModel.availableWidth) + WidgetGrid(placements: model.layout, + availableWidth: model.availableWidth(forDisplay: displayID)) // Im Rumpf gelesen, damit ein Farbwechsel ankommt. .tint(Onyx.Color.accent) .labelStyle(.onyx) diff --git a/Packages/OnyxKit/Sources/OnyxNotch/NSScreen+Metrics.swift b/Packages/OnyxKit/Sources/OnyxNotch/NSScreen+Metrics.swift index 980ebbf..eacef20 100644 --- a/Packages/OnyxKit/Sources/OnyxNotch/NSScreen+Metrics.swift +++ b/Packages/OnyxKit/Sources/OnyxNotch/NSScreen+Metrics.swift @@ -4,7 +4,7 @@ public extension NSScreen { /// Die `CGDirectDisplayID` dieses Bildschirms. Anders als der Index in /// `NSScreen.screens` überlebt sie ein Umstecken oder Umsortieren. - var displayID: CGDirectDisplayID { + public var displayID: CGDirectDisplayID { deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? CGDirectDisplayID ?? 0 }