import SwiftUI import OnyxDesign /// Was am Aussehen einstellbar ist: die Farben und die Bewegung. /// /// Drei Farben, nicht zehn: die Akzentfarbe, mit der Onyx sich selbst zeichnet, /// und das Paar für Herunter- und Hochladen. Die Signalfarben stehen bewusst /// nicht zur Wahl — Gelb heißt Warnung, Rot heißt kritisch, und wer das /// umstellen kann, kann die Bedeutung zerstören. /// /// Die Bewegung kommt als **Profil**, nicht als Federkonstanten. Ausfahren und /// Einfahren müssen zueinander passen; diese Beziehung wählt man, nicht vier /// Zahlen. struct AppearanceSettingsView: View { @Bindable var theme: OnyxTheme /// Ein paar Vorschläge, damit man nicht im Farbwähler anfangen muss. /// /// Alle gedämpft: Onyx ist eine dunkle Fläche, und eine volle Sättigung /// darauf sticht, statt zu leiten. private static let suggestions: [(name: LocalizedStringKey, hex: String)] = [ ("color.suggestion.onyx", "#7FA8FF"), ("color.suggestion.mint", "#6FCF97"), ("color.suggestion.amber", "#E8B44A"), ("color.suggestion.coral", "#E5675C"), ("color.suggestion.lilac", "#B79CFF"), ("color.suggestion.slate", "#9AA6B2"), ] var body: some View { SettingsPage { accent network material motion fixed } } private var accent: some View { OnyxFormCard(String(localized: "color.section.accent")) { OnyxFormRow(String(localized: "color.accent"), hint: String(localized: "color.accent.hint")) { ColorPicker("", selection: $theme.accent, supportsOpacity: false) .labelsHidden() } OnyxFormRow(String(localized: "color.suggestions")) { HStack(spacing: Onyx.Space.row) { ForEach(Self.suggestions, id: \.hex) { suggestion in Swatch(hex: suggestion.hex, name: suggestion.name, isSelected: theme.accent.onyxHex == suggestion.hex) { theme.accent = Color(onyxHex: suggestion.hex) ?? theme.accent } } } } // Mit dem Farbfleck daneben: welche Farbe das System gerade führt, // weiß man nicht auswendig — und dann wirkt das Ergebnis wie ein // Fehler statt wie eine Übernahme. OnyxFormRow(String(localized: "color.adoptSystem")) { Button { theme.adoptSystemAccent() } label: { HStack(spacing: Onyx.Space.sm) { Circle() .fill(Color(nsColor: .controlAccentColor)) .frame(width: 11, height: 11) Text("settings.action.adopt") } } } } } private var network: some View { OnyxFormCard(String(localized: "color.section.network")) { OnyxFormRow(String(localized: "color.download"), hint: String(localized: "color.network.hint")) { ColorPicker("", selection: $theme.download, supportsOpacity: false) .labelsHidden() } OnyxFormRow(String(localized: "color.upload")) { ColorPicker("", selection: $theme.upload, supportsOpacity: false) .labelsHidden() } OnyxFormRow(String(localized: "color.swap")) { Button("settings.action.swap") { let previous = theme.download theme.download = theme.upload theme.upload = previous } } } } private var material: some View { OnyxFormCard(String(localized: "settings.appearance.material"), hint: String(localized: "settings.appearance.material.hint")) { OnyxFormRow(String(localized: "settings.appearance.surface")) { Picker("", selection: $theme.material) { ForEach(NotchMaterial.allCases, id: \.self) { material in Text(material.localizedName).tag(material) } } .labelsHidden() .pickerStyle(.segmented) .frame(width: 240) } } } private var motion: some View { OnyxFormCard(String(localized: "settings.appearance.motion"), hint: String(localized: "settings.appearance.motion.hint")) { OnyxFormRow(String(localized: "settings.appearance.profile")) { Picker("", selection: $theme.motion) { ForEach(MotionProfile.allCases, id: \.self) { profile in Text(profile.localizedName).tag(profile) } } .labelsHidden() .pickerStyle(.segmented) .frame(width: 240) } } } private var fixed: some View { OnyxFormCard(String(localized: "color.section.fixed"), hint: String(localized: "color.fixed.hint")) { // Sichtbar, aber nicht bedienbar: sonst sucht man die Warnfarbe // hier und findet sie nirgends. OnyxFormRow(String(localized: "color.fixed.positive")) { Circle().fill(Onyx.Color.positive).frame(width: 14, height: 14) } OnyxFormRow(String(localized: "color.fixed.warning")) { Circle().fill(Onyx.Color.warning).frame(width: 14, height: 14) } OnyxFormRow(String(localized: "color.fixed.critical")) { Circle().fill(Onyx.Color.critical).frame(width: 14, height: 14) } OnyxFormRow(String(localized: "color.reset")) { Button("settings.action.reset") { theme.reset() } .disabled(theme.isDefault) } } } } extension NotchMaterial { var localizedName: LocalizedStringKey { switch self { case .stone: "settings.material.stone" case .blur: "settings.material.blur" case .glass: "settings.material.glass" } } } extension MotionProfile { var localizedName: LocalizedStringKey { switch self { case .snappy: "settings.motion.snappy" case .bouncy: "settings.motion.bouncy" case .calm: "settings.motion.calm" } } } /// Ein Farbfeld zum Anklicken. private struct Swatch: View { let hex: String let name: LocalizedStringKey let isSelected: Bool let action: () -> Void var body: some View { Button(action: action) { Circle() .fill(Color(onyxHex: hex) ?? .gray) .frame(width: 20, height: 20) .overlay { // Der Ring statt eines Hakens: ein Haken auf einer dunklen // Farbe ist nicht zu sehen, auf einer hellen nicht auf // einer dunklen Fläche. Circle() .strokeBorder(.primary, lineWidth: isSelected ? 2 : 0) .padding(-3) } } .buttonStyle(.plain) .help(Text(name)) } }