Blitz aus der mitgelieferten Vorlage, weiterhin fadend

`bolt.fill` aus SF Symbols ist eine massive Fläche; die neue Datei ist eine
Kontur mit kräftigem Strich und trifft die Form besser.

Gezeichnet wird sie zweifach wie zuvor: einmal etwas größer in der
Gegenfarbe als Rand, einmal in der fadenden Farbe darüber. Damit bleibt der
Blitz in jedem Moment des Fadens lesbar — über der hellen Füllung trägt ihn
der Rand, über dem leeren Teil er sich selbst.

Fehlt die Datei, bleibt das Systemsymbol. Dieselbe Vorlage auch in der
Akku-Kachel, damit nicht zwei verschiedene Blitze in einer App stehen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-08-13 14:05:34 +02:00
parent 49e1a46928
commit 2490a90519
3 changed files with 73 additions and 11 deletions

View File

@@ -68,6 +68,17 @@ public enum BatteryGlyph {
return (filled, empty)
}
/// Der Blitz als eigene Vorlage.
///
/// SF Symbols' `bolt.fill` ist eine massive Fläche; die mitgelieferte
/// Kontur trifft die Form besser und bringt schon einen kräftigen Strich
/// mit. Fehlt die Datei, bleibt das Systemsymbol.
public static let chargingGlyph: NSImage? = {
guard let url = Bundle.main.url(forResource: "thunder", withExtension: "svg")
else { return nil }
return NSImage(contentsOf: url)
}()
/// Wie dick der Rand um das Zeichen ist.
public static let outlineWidth: CGFloat = 1
@@ -186,18 +197,26 @@ public enum BatteryGlyph {
return
}
// Der Blitz: weiß mit dünnem dunklem Rand, und fadend.
// Der Blitz: mit dunklem Rand, und fadend.
//
// Der Rand ist immer die Gegenfarbe zur Füllung. Damit bleibt er in
// **jedem** Moment des Fadens lesbar: über der hellen Füllung trägt ihn
// der Rand, über dem leeren Teil die Füllung selbst.
// Der Rand ist immer die Gegenfarbe zum Blitz selbst. Damit bleibt er
// in **jedem** Moment des Fadens lesbar: über der hellen Füllung trägt
// ihn der Rand, über dem leeren Teil der Blitz.
let whiteness = boltWhiteness(phase: fadePhase)
if let outline = tint(glyph, with: NSColor(white: 1 - whiteness, alpha: 1)) {
let inverseColor = NSColor(white: 1 - whiteness, alpha: 1)
let boltColor = NSColor(white: whiteness, alpha: 1)
if let custom = chargingGlyph {
MenuBarText.drawTemplate(custom, color: inverseColor, in: outlineRect(glyphRect))
MenuBarText.drawTemplate(custom, color: boltColor, in: glyphRect)
return
}
if let outline = tint(glyph, with: inverseColor) {
outline.draw(in: outlineRect(glyphRect), from: .zero,
operation: .sourceOver, fraction: 1)
}
if let body = tint(glyph, with: NSColor(white: whiteness, alpha: 1)) {
body.draw(in: glyphRect, from: .zero, operation: .sourceOver, fraction: 1)
if let filled = tint(glyph, with: boltColor) {
filled.draw(in: glyphRect, from: .zero, operation: .sourceOver, fraction: 1)
}
}
}
@@ -258,11 +277,9 @@ public struct BatteryGlyphView: View {
/ BatteryGlyph.fadeDuration
let whiteness = BatteryGlyph.boltWhiteness(phase: phase)
ZStack {
Image(systemName: name)
.font(.system(size: size + 2 * BatteryGlyph.outlineWidth))
bolt(name: name, side: size + 2 * BatteryGlyph.outlineWidth)
.foregroundStyle(Color(white: 1 - whiteness))
Image(systemName: name)
.font(.system(size: size))
bolt(name: name, side: size)
.foregroundStyle(Color(white: whiteness))
}
}
@@ -282,6 +299,20 @@ public struct BatteryGlyphView: View {
.frame(width: width, height: height)
}
/// Dieselbe Vorlage wie in der Menüleiste, sonst das Systemsymbol.
@ViewBuilder
private func bolt(name: String, side: CGFloat) -> some View {
if let custom = BatteryGlyph.chargingGlyph {
Image(nsImage: custom)
.renderingMode(.template)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: side, height: side)
} else {
Image(systemName: name).font(.system(size: side))
}
}
private var fillColor: Color {
BatteryGlyph.isLow(charge: charge, isCharging: isCharging) ? .red : .primary
}