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:
@@ -41,6 +41,8 @@ let package = Package(
|
||||
resources: [.process("Localizable.xcstrings")]),
|
||||
.testTarget(name: "OnyxNotchTests", dependencies: ["OnyxNotch"]),
|
||||
|
||||
.testTarget(name: "OnyxDesignTests", dependencies: ["OnyxDesign"]),
|
||||
|
||||
.target(name: "OnyxWidgetKit", dependencies: ["OnyxDesign"]),
|
||||
.testTarget(name: "OnyxWidgetKitTests", dependencies: ["OnyxWidgetKit"]),
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import SwiftUI
|
||||
import OnyxDesign
|
||||
|
||||
/// Die Wahl der Terminquelle — samt ehrlicher Auskunft darüber, was Calendarr
|
||||
/// gerade liefert.
|
||||
@@ -73,7 +74,7 @@ public struct CalendarSourceSettings: View {
|
||||
notice("calendar.source.status.incompatible")
|
||||
case .unreadable(let reason):
|
||||
Text("calendar.source.status.unreadable \(reason)", bundle: .module)
|
||||
.font(.callout).foregroundStyle(.orange)
|
||||
.font(.callout).foregroundStyle(Onyx.Color.warning)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
case .denied, .none:
|
||||
ProgressView().controlSize(.small)
|
||||
|
||||
@@ -40,7 +40,7 @@ private struct SectionTitle: View {
|
||||
var body: some View {
|
||||
Text(text, bundle: .module)
|
||||
.font(.caption.weight(.semibold))
|
||||
.foregroundStyle(.tint)
|
||||
.foregroundStyle(Onyx.Color.accent)
|
||||
.textCase(.uppercase)
|
||||
}
|
||||
}
|
||||
@@ -81,7 +81,9 @@ private struct Dial: View {
|
||||
let value: Double
|
||||
let caption: String
|
||||
let text: String
|
||||
var tint: Color = .accentColor
|
||||
/// Ohne Angabe die Akzentfarbe. Als Standardwert ginge sie nicht: der
|
||||
/// wird außerhalb des Hauptthreads erzeugt, die Farbe liegt aber dort.
|
||||
var tint: Color?
|
||||
|
||||
var body: some View {
|
||||
// Sechs Punkte Abstand statt zwei: die Beschriftung klebte am Ring und
|
||||
@@ -91,7 +93,8 @@ private struct Dial: View {
|
||||
Circle().stroke(.quaternary, lineWidth: 5)
|
||||
Circle()
|
||||
.trim(from: 0, to: min(max(value, 0), 1))
|
||||
.stroke(tint, style: .init(lineWidth: 5, lineCap: .round))
|
||||
.stroke(tint ?? Onyx.Color.accent,
|
||||
style: .init(lineWidth: 5, lineCap: .round))
|
||||
.rotationEffect(.degrees(-90))
|
||||
Text(text).font(.system(size: 15, weight: .medium).monospacedDigit())
|
||||
}
|
||||
@@ -182,10 +185,10 @@ private struct CoreGrid: View {
|
||||
spacing: 3) {
|
||||
ForEach(Array(values.enumerated()), id: \.offset) { _, value in
|
||||
RoundedRectangle(cornerRadius: 2, style: .continuous)
|
||||
.fill(Color.accentColor.opacity(0.18))
|
||||
.fill(Onyx.Color.accent.opacity(0.18))
|
||||
.overlay(alignment: .bottom) {
|
||||
RoundedRectangle(cornerRadius: 2, style: .continuous)
|
||||
.fill(Color.accentColor)
|
||||
.fill(Onyx.Color.accent)
|
||||
.frame(height: max(14 * value, 1.5))
|
||||
}
|
||||
.frame(height: 14)
|
||||
@@ -208,7 +211,7 @@ private struct MemoryDetail: View {
|
||||
text: MetricFormat.percent(memory.usedFraction),
|
||||
tint: pressureColor)
|
||||
VStack(alignment: .leading, spacing: 3) {
|
||||
legend(.accentColor, "popover.memory.app", memory.app)
|
||||
legend(Onyx.Color.accent, "popover.memory.app", memory.app)
|
||||
legend(.purple, "popover.memory.wired", memory.wired)
|
||||
legend(.teal, "popover.memory.compressed", memory.compressed)
|
||||
legend(.gray, "popover.memory.free", memory.free)
|
||||
@@ -220,7 +223,7 @@ private struct MemoryDetail: View {
|
||||
// ist ein voller Speicher kein Problem — macOS füllt ihn absichtlich.
|
||||
Row(label: String(localized: "popover.memory.swapUsed", bundle: .module),
|
||||
value: "\(bytes(memory.swapUsed)) / \(bytes(memory.swapTotal))",
|
||||
emphasis: memory.swapUsed > 512 * 1024 * 1024 ? .orange : nil)
|
||||
emphasis: memory.swapUsed > 512 * 1024 * 1024 ? Onyx.Color.warning : nil)
|
||||
|
||||
if !snapshot.topMemoryProcesses.isEmpty {
|
||||
SectionTitle(text: "popover.processes")
|
||||
@@ -232,9 +235,9 @@ private struct MemoryDetail: View {
|
||||
|
||||
private var pressureColor: Color {
|
||||
switch snapshot.memory.pressure {
|
||||
case .normal: .green
|
||||
case .warning: .orange
|
||||
case .critical: .red
|
||||
case .normal: Onyx.Color.positive
|
||||
case .warning: Onyx.Color.warning
|
||||
case .critical: Onyx.Color.critical
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,8 +267,9 @@ private struct BatteryDetail: View {
|
||||
Dial(value: battery.charge,
|
||||
caption: String(localized: "metric.battery", bundle: .module),
|
||||
text: MetricFormat.percent(battery.charge),
|
||||
tint: battery.isCharging ? .green
|
||||
: (battery.charge < 0.2 ? .red : .accentColor))
|
||||
tint: battery.isCharging ? Onyx.Color.positive
|
||||
: (battery.charge < 0.2 ? Onyx.Color.critical
|
||||
: Onyx.Color.accent))
|
||||
// Beides gehört neben den Ring: die Schätzung und der Grund
|
||||
// dafür. „Am Netzteil" über dem Ring und „Am Netz, lädt nicht"
|
||||
// darunter waren zweimal dieselbe Auskunft an zwei Stellen —
|
||||
@@ -382,7 +386,7 @@ private struct BatteryDetail: View {
|
||||
private func tint(for activity: ChargeActivity) -> Color {
|
||||
// Farbe nur, wo sie etwas meldet: Laden ist ein Zustand, den man
|
||||
// sucht. Alles andere bleibt zurückhaltend.
|
||||
activity == .charging ? .green : .secondary
|
||||
activity == .charging ? Onyx.Color.positive : .secondary
|
||||
}
|
||||
|
||||
private func duration(_ seconds: TimeInterval) -> String {
|
||||
|
||||
@@ -199,7 +199,7 @@
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Je Programm"
|
||||
"value": "Programme"
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import SwiftUI
|
||||
import MetricsProvider
|
||||
import OnyxDesign
|
||||
import OnyxMenuBar
|
||||
|
||||
@MainActor
|
||||
@@ -9,9 +10,9 @@ struct NetworkPopoverContent: View {
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
HStack(alignment: .top) {
|
||||
rate(model.snapshot.downloadRate, "network.download", .green)
|
||||
rate(model.snapshot.downloadRate, "network.download", Onyx.Color.download)
|
||||
Spacer()
|
||||
rate(model.snapshot.uploadRate, "network.upload", .pink)
|
||||
rate(model.snapshot.uploadRate, "network.upload", Onyx.Color.upload)
|
||||
}
|
||||
|
||||
TrafficChart(history: model.history)
|
||||
@@ -91,9 +92,11 @@ struct NetworkPopoverContent: View {
|
||||
Text(process.name).font(.callout).lineLimit(1)
|
||||
Spacer(minLength: 8)
|
||||
Text(Throughput.formatted(process.downloadRate))
|
||||
.font(.caption.monospacedDigit()).foregroundStyle(.green)
|
||||
.font(.caption.monospacedDigit())
|
||||
.foregroundStyle(Onyx.Color.download)
|
||||
Text(Throughput.formatted(process.uploadRate))
|
||||
.font(.caption.monospacedDigit()).foregroundStyle(.pink)
|
||||
.font(.caption.monospacedDigit())
|
||||
.foregroundStyle(Onyx.Color.upload)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,7 +137,7 @@ struct NetworkPopoverContent: View {
|
||||
private func section(_ key: LocalizedStringKey) -> some View {
|
||||
Text(key, bundle: .module)
|
||||
.font(.caption.weight(.semibold))
|
||||
.foregroundStyle(.tint)
|
||||
.foregroundStyle(Onyx.Color.accent)
|
||||
.textCase(.uppercase)
|
||||
}
|
||||
|
||||
@@ -183,11 +186,11 @@ private struct TrafficChart: View {
|
||||
ForEach(Array(points.enumerated()), id: \.offset) { _, snapshot in
|
||||
VStack(spacing: 1) {
|
||||
Rectangle()
|
||||
.fill(.pink)
|
||||
.fill(Onyx.Color.upload)
|
||||
.frame(height: max(half * CGFloat(snapshot.uploadRate / peak), 0.5))
|
||||
.frame(maxHeight: .infinity, alignment: .bottom)
|
||||
Rectangle()
|
||||
.fill(.green)
|
||||
.fill(Onyx.Color.download)
|
||||
.frame(height: max(half * CGFloat(snapshot.downloadRate / peak), 0.5))
|
||||
.frame(maxHeight: .infinity, alignment: .top)
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ private struct NetworkWidgetView: View {
|
||||
HStack(spacing: 6) {
|
||||
Image(systemName: model.snapshot.interfaceKind.symbolName)
|
||||
.font(.system(size: 11))
|
||||
.foregroundStyle(Onyx.Color.accent)
|
||||
.foregroundStyle(Onyx.Color.download)
|
||||
Text(connectionName)
|
||||
.font(Onyx.Font.caption)
|
||||
.foregroundStyle(Onyx.Color.textSecondary)
|
||||
@@ -154,7 +154,7 @@ private struct NetworkWidgetView: View {
|
||||
|
||||
Rates(snapshot: model.snapshot, compact: false)
|
||||
|
||||
Sparkline(values: model.series(download: true), tint: Onyx.Color.accent,
|
||||
Sparkline(values: model.series(download: true), tint: Onyx.Color.download,
|
||||
capacity: NetworkModel.historyLength)
|
||||
.frame(height: 18)
|
||||
|
||||
@@ -190,8 +190,8 @@ private struct Rates: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 1) {
|
||||
row("arrow.down", Throughput.formatted(snapshot.downloadRate), Onyx.Color.accent)
|
||||
row("arrow.up", Throughput.formatted(snapshot.uploadRate), Onyx.Color.positive)
|
||||
row("arrow.down", Throughput.formatted(snapshot.downloadRate), Onyx.Color.download)
|
||||
row("arrow.up", Throughput.formatted(snapshot.uploadRate), Onyx.Color.upload)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,22 @@ public enum Onyx {
|
||||
public static let textSecondary = SwiftUI.Color.white.opacity(0.58)
|
||||
public static let textTertiary = SwiftUI.Color.white.opacity(0.34)
|
||||
|
||||
/// Kühl und entsättigt. Farbe ist in Onyx ein Signal, keine Dekoration.
|
||||
public static let accent = SwiftUI.Color(red: 0.498, green: 0.659, blue: 1.0)
|
||||
/// Die Farbe, mit der Onyx sich selbst zeichnet — einstellbar.
|
||||
///
|
||||
/// Gelesen statt gespeichert: die Tokens holen den Wert bei jedem
|
||||
/// Zugriff aus `OnyxTheme`. Damit merkt SwiftUI die Abhängigkeit und
|
||||
/// zeichnet neu, sobald sich die Farbe ändert — bei einer Konstanten
|
||||
/// wären alle vierzig Fundstellen einzeln umzustellen gewesen.
|
||||
@MainActor public static var accent: SwiftUI.Color { OnyxTheme.shared.accent }
|
||||
|
||||
/// Herunter- und Hochladen. Auch einstellbar: welche Richtung grün ist
|
||||
/// und welche rosa, ist Gewohnheit, keine Bedeutung.
|
||||
@MainActor public static var download: SwiftUI.Color { OnyxTheme.shared.download }
|
||||
@MainActor public static var upload: SwiftUI.Color { OnyxTheme.shared.upload }
|
||||
|
||||
/// Die Signalfarben bleiben fest. Gelb heißt Warnung, Rot heißt
|
||||
/// kritisch, Grün heißt in Ordnung — wer das umstellen kann, kann die
|
||||
/// Bedeutung zerstören.
|
||||
public static let positive = SwiftUI.Color(red: 0.435, green: 0.812, blue: 0.592)
|
||||
public static let warning = SwiftUI.Color(red: 0.910, green: 0.706, blue: 0.290)
|
||||
public static let critical = SwiftUI.Color(red: 0.898, green: 0.404, blue: 0.361)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import AppKit
|
||||
import OnyxDesign
|
||||
import SwiftUI
|
||||
|
||||
/// Verwaltet beliebig viele Menüleisten-Elemente, je eines pro aktiviertem Modul.
|
||||
@@ -139,8 +140,12 @@ public final class MenuBarController {
|
||||
}
|
||||
closeOpenPopover()
|
||||
|
||||
// Die Akzentfarbe hier und nicht in jedem Popover einzeln: `.tint`
|
||||
// vererbt sich nach unten, und ohne sie fielen Haken, Regler und
|
||||
// Auswahlfelder auf die Systemfarbe zurück — das war der Grund, warum
|
||||
// die Popovers grün waren und die Widgets blau.
|
||||
entry.popover.contentViewController = NSHostingController(
|
||||
rootView: entry.module.makePopoverView())
|
||||
rootView: entry.module.makePopoverView().tint(Onyx.Color.accent))
|
||||
entry.popover.show(relativeTo: sender.bounds, of: sender, preferredEdge: .minY)
|
||||
// Ohne das bleibt das Popover hinter dem aktiven Fenster.
|
||||
entry.popover.contentViewController?.view.window?.makeKey()
|
||||
|
||||
76
Packages/OnyxKit/Tests/OnyxDesignTests/OnyxThemeTests.swift
Normal file
76
Packages/OnyxKit/Tests/OnyxDesignTests/OnyxThemeTests.swift
Normal file
@@ -0,0 +1,76 @@
|
||||
import Testing
|
||||
import SwiftUI
|
||||
@testable import OnyxDesign
|
||||
|
||||
@Suite("Farbwahl")
|
||||
@MainActor
|
||||
struct OnyxThemeTests {
|
||||
|
||||
private func freshDefaults(_ name: String) -> UserDefaults {
|
||||
let suite = "onyx.theme.test.\(name)"
|
||||
let defaults = UserDefaults(suiteName: suite)!
|
||||
defaults.removePersistentDomain(forName: suite)
|
||||
return defaults
|
||||
}
|
||||
|
||||
@Test("Ohne Einstellung stehen die Werkswerte")
|
||||
func defaultsAreTheFactoryColours() {
|
||||
let theme = OnyxTheme(defaults: freshDefaults(#function))
|
||||
#expect(theme.accent.onyxHex == OnyxTheme.defaultAccent.onyxHex)
|
||||
#expect(theme.isDefault)
|
||||
}
|
||||
|
||||
@Test("Eine gewählte Farbe übersteht den Neustart")
|
||||
func choiceSurvivesRestart() {
|
||||
let defaults = freshDefaults(#function)
|
||||
let theme = OnyxTheme(defaults: defaults)
|
||||
theme.accent = Color(onyxHex: "#FF8800")!
|
||||
|
||||
let restarted = OnyxTheme(defaults: defaults)
|
||||
#expect(restarted.accent.onyxHex == "#FF8800")
|
||||
#expect(!restarted.isDefault)
|
||||
}
|
||||
|
||||
@Test("Herunter und Hoch sind getrennt einstellbar")
|
||||
func directionsAreIndependent() {
|
||||
let theme = OnyxTheme(defaults: freshDefaults(#function))
|
||||
theme.download = Color(onyxHex: "#112233")!
|
||||
#expect(theme.download.onyxHex == "#112233")
|
||||
#expect(theme.upload.onyxHex == OnyxTheme.defaultUpload.onyxHex)
|
||||
}
|
||||
|
||||
@Test("Zurücksetzen stellt alles wieder her")
|
||||
func resetRestoresEverything() {
|
||||
let theme = OnyxTheme(defaults: freshDefaults(#function))
|
||||
theme.accent = Color(onyxHex: "#000000")!
|
||||
theme.download = Color(onyxHex: "#000000")!
|
||||
theme.upload = Color(onyxHex: "#000000")!
|
||||
theme.reset()
|
||||
#expect(theme.isDefault)
|
||||
}
|
||||
}
|
||||
|
||||
@Suite("Farben als Text")
|
||||
struct ColourHexTests {
|
||||
|
||||
@Test("Hin und zurück ergibt denselben Wert")
|
||||
func roundTrip() {
|
||||
for hex in ["#000000", "#FFFFFF", "#7FA8FF", "#6FCF97", "#EF6D9C"] {
|
||||
#expect(Color(onyxHex: hex)?.onyxHex == hex)
|
||||
}
|
||||
}
|
||||
|
||||
@Test("Ohne Doppelkreuz geht es auch")
|
||||
func hashIsOptional() {
|
||||
#expect(Color(onyxHex: "7FA8FF")?.onyxHex == "#7FA8FF")
|
||||
}
|
||||
|
||||
@Test("Unsinn ergibt keine Farbe")
|
||||
func garbageIsRejected() {
|
||||
// Sonst stünde nach einer verkorksten Einstellung Schwarz auf Schwarz.
|
||||
#expect(Color(onyxHex: nil) == nil)
|
||||
#expect(Color(onyxHex: "") == nil)
|
||||
#expect(Color(onyxHex: "#12345") == nil)
|
||||
#expect(Color(onyxHex: "#GGGGGG") == nil)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user