Panel je Bildschirm messen, nicht am schmalsten
Die Breite kam vom **schmalsten** aktiven Bildschirm — mit der Begründung, dasselbe Panel erscheine überall. Ein Fernseher widerlegt das: bei 720 Punkten Breite bleiben drei Spalten, vier Widgets brechen auf zwei Reihen um, und das Panel wird hoch statt breit. Auf dem Notebook, wo achtmal Platz gewesen wäre. Jetzt rechnet jeder Bildschirm für sich. Der Koordinator kennt seine Bildschirme ohnehin und reicht die Kennung schon durch — sie wurde nur nicht benutzt. Die zweite Hälfte war, dass es nach dem Abstecken so blieb: die Größe wurde einmal gerechnet und nie wieder. `NSScreen` meldet sich nicht bei SwiftUI, also gibt es jetzt einen mitgezählten Bildschirmwechsel, den die Ansicht mitliest, und beim Wechsel werden die Fenstergrößen neu gesetzt — verzögert um einen Durchlauf, damit der Koordinator seine Fenster vorher neu aufgebaut hat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user