import SwiftUI import AppKit /// Die Vibrancy-Ebene hinter dem Panel. /// /// `.hudWindow` mit `behindWindow` lässt den Hintergrund durchscheinen, ohne ihn /// lesbar zu lassen. Der Tint darüber macht daraus schwarzen Stein statt grauem Glas. public struct OnyxVisualEffect: NSViewRepresentable { public init() {} public func makeNSView(context: Context) -> NSVisualEffectView { let view = NSVisualEffectView() view.material = .hudWindow view.blendingMode = .behindWindow view.state = .active view.appearance = NSAppearance(named: .darkAqua) return view } public func updateNSView(_ view: NSVisualEffectView, context: Context) {} } /// Der Stein selbst — auf einer beliebigen Form. /// /// Tint, Bänderung, Kante. Drei Lagen, die zusammen den Werkstoff ergeben, und /// zwar unabhängig davon, welche Form sie annehmen. /// /// **Warum generisch und nicht einfach ein abgerundetes Rechteck.** Das Panel /// zeichnet auf ``NotchShape``, einer Form, die sich beim Aus- und Einfahren /// verändert. Solange das Rezept an ein Rechteck gebunden war, konnte es dort /// nicht mitbenutzt werden — also stand es zweimal da, einmal hier und einmal /// im Panel. Zweimal heißt: eine Änderung an der Bänderung wirkt an einer /// Stelle und an der anderen nicht, und das fällt erst auf, wenn beide /// nebeneinander zu sehen sind. public struct OnyxStone: View { private let shape: S public init(_ shape: S) { self.shape = shape } public var body: some View { // Aus dem Thema gelesen und nicht als Argument gereicht: sonst müsste // jede Stelle, die eine Fläche zeichnet, den Werkstoff durchschleifen — // bis hinunter zur Kachel, die ihn gar nicht kennt. let material = OnyxTheme.shared.material shape .fill(Onyx.Color.surface.opacity(material.fillOpacity)) .overlay { // Die Bänderung des Steins: oben ein Hauch heller, nach 40 % // vollständig verlaufen. Mehr wirkt sofort billig — wie viel // „ein Hauch" ist, hängt am Werkstoff. shape.fill( LinearGradient( stops: [ .init(color: .white.opacity(material.sheen), location: 0), .init(color: .clear, location: 0.4), ], startPoint: .top, endPoint: .bottom)) } .overlay { // `strokeBorder` und nicht `stroke`: die Linie liegt dann // vollständig **innen**. Mittig gezeichnet ragte sie zur Hälfte // über die Kante hinaus und wurde an den Rundungen sichtbar // dünner. shape.strokeBorder(material.edge, lineWidth: Onyx.Metric.hairlineWidth) } } } /// Der Hintergrund des Panels: Vibrancy und darauf der Stein. public struct OnyxSurface: View { private let cornerRadius: CGFloat public init(cornerRadius: CGFloat = Onyx.Radius.panel) { self.cornerRadius = cornerRadius } public var body: some View { let shape = RoundedRectangle(cornerRadius: cornerRadius, style: .continuous) OnyxStone(shape) .background { // Bei deckendem Stein trägt die Weichzeichnung nichts bei und // wird auch nicht berechnet. if OnyxTheme.shared.material.showsVibrancy { OnyxVisualEffect().clipShape(shape) } } } } /// Eine Kachel auf der Panelfläche. public struct OnyxTile: View { private let content: Content public init(@ViewBuilder content: () -> Content) { self.content = content() } public var body: some View { let shape = RoundedRectangle(cornerRadius: Onyx.Radius.tile, style: .continuous) content .padding(Onyx.Space.lg) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) .background(shape.fill(Onyx.Color.elevated)) .overlay(shape.strokeBorder(Onyx.Color.hairline, lineWidth: Onyx.Metric.hairlineWidth)) } }