Alle Kacheln, Popovers und Fenster auf die Designschicht umgestellt: rund hundert gesetzte Schriftgrößen und 159 gesetzte Abstände und Radien sind jetzt Token. Übrig bleibt `spacing: 0` — das ist eine Aussage, keine geratene Zahl. Die Einstellungen bekommen eine Seitenleiste statt sieben Reitern, deren deutsche Beschriftungen zusammen 880 Punkte brauchten. Panel-Token stehen nicht mehr in Fenstern: dort gilt Systemsemantik, damit der Hellmodus nicht schwarz auf schwarz endet. Neu ist ein Schaufenster, das alle Bauteile nebeneinander zeigt — ohne das lässt sich eine Änderung an einem Token nur prüfen, indem man die App bedient, bis jede Kachel einmal zu sehen war. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
62 lines
2.4 KiB
Swift
62 lines
2.4 KiB
Swift
import SwiftUI
|
|
import OnyxDesign
|
|
|
|
/// Zeichnet ein aufgelöstes Layout.
|
|
///
|
|
/// Die Positionen kommen aus `LayoutEngine`, nicht aus einem SwiftUI-Grid. Ein
|
|
/// `LazyVGrid` kann keine Kacheln über zwei Zeilen führen, und genau das braucht
|
|
/// der Mini-Monat.
|
|
public struct WidgetGrid: View {
|
|
|
|
private let resolved: [ResolvedPlacement]
|
|
private let panelSize: CGSize
|
|
|
|
public init(placements: [WidgetPlacement], availableWidth: CGFloat) {
|
|
self.resolved = LayoutEngine.resolve(placements, availableWidth: availableWidth)
|
|
self.panelSize = LayoutEngine.panelSize(for: placements, availableWidth: availableWidth)
|
|
}
|
|
|
|
public var body: some View {
|
|
ZStack(alignment: .topLeading) {
|
|
ForEach(resolved) { item in
|
|
let frame = LayoutEngine.frame(for: item)
|
|
|
|
widgetView(for: item)
|
|
.frame(width: frame.width, height: frame.height)
|
|
// Beschneiden ist Pflicht, kein Feinschliff: quillt der
|
|
// Inhalt über, zeichnet er über die Nachbarkachel — und
|
|
// ganz oben über die Notch.
|
|
.clipped()
|
|
.offset(x: frame.minX, y: frame.minY)
|
|
}
|
|
}
|
|
.frame(width: panelSize.width, height: panelSize.height, alignment: .topLeading)
|
|
}
|
|
|
|
@ViewBuilder
|
|
private func widgetView(for item: ResolvedPlacement) -> some View {
|
|
if let widget = WidgetRegistry.shared.widget(id: item.placement.widgetID) {
|
|
OnyxTile { widget.makeView() }
|
|
} else {
|
|
// Sollte nach dem Filtern in LayoutStore nicht vorkommen. Falls doch,
|
|
// ist eine sichtbare Lücke mit Namen besser als ein stiller Ausfall.
|
|
OnyxTile {
|
|
VStack(spacing: Onyx.Space.xs) {
|
|
Image(systemName: "questionmark.square.dashed")
|
|
Text(item.placement.widgetID)
|
|
.font(Onyx.Font.caption)
|
|
}
|
|
.foregroundStyle(Onyx.Color.textTertiary)
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Die Größe, die das Panel für dieses Layout braucht — der Controller
|
|
/// stellt das Fenster darauf ein.
|
|
public static func panelSize(for placements: [WidgetPlacement],
|
|
availableWidth: CGFloat) -> CGSize {
|
|
LayoutEngine.panelSize(for: placements, availableWidth: availableWidth)
|
|
}
|
|
}
|