Farben einstellbar — und erst mal überhaupt eine Farbe
Das Grün, das überall auftauchte, war die System-Akzentfarbe: `.tint` und `.accentColor` werden ohne eigene Angabe dorthin durchgereicht, und das betraf alle Popovers und das ganze Einstellungsfenster. Die Notch-Widgets dagegen benutzten Onyx' eigenes Blau. Zwei Farben, ungewollt. Jetzt eine, und die ist einstellbar. Ein neuer Reiter „Farben": Akzentfarbe mit sechs Vorschlägen und einem Knopf, der die Systemfarbe übernimmt, dazu das Paar für Herunter- und Hochladen samt Tauschknopf. Die Signalfarben bleiben fest und stehen nur zur Ansicht dort. Gelb heißt Warnung, Rot heißt kritisch, Grün heißt in Ordnung — bei Temperatur, Akku, Lüfter und Fehlern. Wer sie umstellen kann, kann ihre Bedeutung zerstören; ein rotes „alles gut" liest niemand richtig. Sichtbar sind sie trotzdem, sonst sucht man die Warnfarbe im Reiter und findet sie nirgends. Umgesetzt über die Tokens statt über vierzig Fundstellen: `Onyx.Color.accent` ist keine Konstante mehr, sondern liest bei jedem Zugriff aus `OnyxTheme`. Damit merkt SwiftUI die Abhängigkeit und zeichnet neu, sobald die Farbe sich ändert. Dazu ein `.tint` an den vier Wurzeln — Panel, Popovers, Einstellungen, Einrichtung —, damit auch Haken, Regler und Auswahlfelder mitziehen. Nebenbei die Beschriftung: „Je Programm" heißt jetzt „Programme". Großgesetzt las sich das als englisches „J-E". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
106
Packages/OnyxKit/Sources/OnyxDesign/OnyxTheme.swift
Normal file
106
Packages/OnyxKit/Sources/OnyxDesign/OnyxTheme.swift
Normal file
@@ -0,0 +1,106 @@
|
||||
import SwiftUI
|
||||
import AppKit
|
||||
|
||||
/// Die einstellbaren Farben.
|
||||
///
|
||||
/// **Nicht alle Farben sind einstellbar, und das ist Absicht.** Gelb heißt
|
||||
/// Warnung, Rot heißt kritisch, Grün heißt in Ordnung — bei Temperatur, Akku,
|
||||
/// Lüfter und Fehlermeldungen. Wer sie umstellen kann, kann ihre Bedeutung
|
||||
/// zerstören; ein rotes „alles gut" liest niemand richtig. Einstellbar ist
|
||||
/// deshalb die Akzentfarbe, mit der Onyx sich selbst zeichnet, und das Paar für
|
||||
/// Herunter- und Hochladen — dort ist die Zuordnung willkürlich und nur eine
|
||||
/// Gewohnheit.
|
||||
@MainActor
|
||||
@Observable
|
||||
public final class OnyxTheme {
|
||||
|
||||
/// Die Instanz, aus der `Onyx.Color` liest.
|
||||
public static let shared = OnyxTheme()
|
||||
|
||||
/// Kühl und entsättigt — Farbe ist in Onyx ein Signal, keine Dekoration.
|
||||
public static let defaultAccent = Color(red: 0.498, green: 0.659, blue: 1.0)
|
||||
/// Herunterladen grün, Hochladen rosa: die Zuordnung ist reine Gewohnheit,
|
||||
/// aber eine verbreitete. Wer sie anders kennt, dreht sie um.
|
||||
public static let defaultDownload = Color(red: 0.435, green: 0.812, blue: 0.592)
|
||||
public static let defaultUpload = Color(red: 0.937, green: 0.427, blue: 0.612)
|
||||
|
||||
public var accent: Color {
|
||||
didSet { store(accent, as: Keys.accent) }
|
||||
}
|
||||
public var download: Color {
|
||||
didSet { store(download, as: Keys.download) }
|
||||
}
|
||||
public var upload: Color {
|
||||
didSet { store(upload, as: Keys.upload) }
|
||||
}
|
||||
|
||||
private let defaults: UserDefaults
|
||||
|
||||
private enum Keys {
|
||||
static let accent = "onyx.theme.accent"
|
||||
static let download = "onyx.theme.download"
|
||||
static let upload = "onyx.theme.upload"
|
||||
}
|
||||
|
||||
public init(defaults: UserDefaults = .standard) {
|
||||
self.defaults = defaults
|
||||
accent = Color(onyxHex: defaults.string(forKey: Keys.accent)) ?? Self.defaultAccent
|
||||
download = Color(onyxHex: defaults.string(forKey: Keys.download)) ?? Self.defaultDownload
|
||||
upload = Color(onyxHex: defaults.string(forKey: Keys.upload)) ?? Self.defaultUpload
|
||||
}
|
||||
|
||||
/// Ob alles auf den Werkseinstellungen steht — für den Zurücksetzen-Knopf,
|
||||
/// der sonst auch dann anböte, etwas zu tun, wenn es nichts zu tun gibt.
|
||||
public var isDefault: Bool {
|
||||
accent.onyxHex == Self.defaultAccent.onyxHex
|
||||
&& download.onyxHex == Self.defaultDownload.onyxHex
|
||||
&& upload.onyxHex == Self.defaultUpload.onyxHex
|
||||
}
|
||||
|
||||
public func reset() {
|
||||
accent = Self.defaultAccent
|
||||
download = Self.defaultDownload
|
||||
upload = Self.defaultUpload
|
||||
}
|
||||
|
||||
/// Übernimmt die Akzentfarbe des Systems.
|
||||
///
|
||||
/// Genau die stand hier vorher an vielen Stellen — unfreiwillig, weil
|
||||
/// `.tint` und `.accentColor` ohne eigene Angabe dorthin durchgereicht
|
||||
/// werden. Wem das gefiel, soll es behalten können.
|
||||
public func adoptSystemAccent() {
|
||||
accent = Color(nsColor: .controlAccentColor)
|
||||
}
|
||||
|
||||
private func store(_ color: Color, as key: String) {
|
||||
defaults.set(color.onyxHex, forKey: key)
|
||||
}
|
||||
}
|
||||
|
||||
public extension Color {
|
||||
|
||||
/// `#RRGGBB`, immer in sRGB.
|
||||
///
|
||||
/// Der Umweg über `NSColor` ist nötig, weil eine `Color` aus dem Farbwähler
|
||||
/// in einem beliebigen Farbraum liegen kann — ohne Umrechnung ergäbe die
|
||||
/// Zerlegung in Komponenten je nach Bildschirm etwas anderes.
|
||||
var onyxHex: String {
|
||||
guard let srgb = NSColor(self).usingColorSpace(.sRGB) else { return "#7FA8FF" }
|
||||
let red = Int((srgb.redComponent * 255).rounded())
|
||||
let green = Int((srgb.greenComponent * 255).rounded())
|
||||
let blue = Int((srgb.blueComponent * 255).rounded())
|
||||
return String(format: "#%02X%02X%02X", red, green, blue)
|
||||
}
|
||||
|
||||
/// Liest `#RRGGBB` und `RRGGBB`.
|
||||
init?(onyxHex: String?) {
|
||||
guard let onyxHex else { return nil }
|
||||
var text = onyxHex.trimmingCharacters(in: .whitespaces)
|
||||
if text.hasPrefix("#") { text.removeFirst() }
|
||||
guard text.count == 6, let value = UInt32(text, radix: 16) else { return nil }
|
||||
self.init(.sRGB,
|
||||
red: Double((value >> 16) & 0xFF) / 255,
|
||||
green: Double((value >> 8) & 0xFF) / 255,
|
||||
blue: Double(value & 0xFF) / 255)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user