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

@@ -27,6 +27,7 @@
9C2E419014B7E04630FDF151 /* OnyxApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89CAC2A7DA89B8707D45D65 /* OnyxApp.swift */; };
9EC9D57A96489F5578000822 /* NetworkProvider in Frameworks */ = {isa = PBXBuildFile; productRef = FF4E99C8BF0D8944C314A8AF /* NetworkProvider */; };
A529046C33E6B00C0E9508FF /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 508DECB5C05264E6130C373E /* Localizable.xcstrings */; };
A89903046D47175EE68392FA /* thunder.svg in Resources */ = {isa = PBXBuildFile; fileRef = 19F96370F33A525437F6E691 /* thunder.svg */; };
A9617230F602C5D52F63A7E2 /* ShelfProvider in Frameworks */ = {isa = PBXBuildFile; productRef = FA6C9F90DF47DC0DFB9ED55D /* ShelfProvider */; };
B0FCCD92AF22F1636482EACD /* sound-waves.svg in Resources */ = {isa = PBXBuildFile; fileRef = 8BCDFD427ACD83B8AA7BA39E /* sound-waves.svg */; };
B309DD8966B5348250CEC6BD /* LaunchAtLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41C778876BEF3D8B04F7C3C9 /* LaunchAtLogin.swift */; };
@@ -58,6 +59,7 @@
0804288DC4A8146DD9F3FC3E /* Onyx.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Onyx.app; sourceTree = BUILT_PRODUCTS_DIR; };
085D860E654E09E4CD6E535A /* chip.svg */ = {isa = PBXFileReference; path = chip.svg; sourceTree = "<group>"; };
16F2B1B9A74594526F34F5C9 /* WidgetOptionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetOptionsView.swift; sourceTree = "<group>"; };
19F96370F33A525437F6E691 /* thunder.svg */ = {isa = PBXFileReference; path = thunder.svg; sourceTree = "<group>"; };
1E6A1C208C7EA56971A055E1 /* graphic-card.svg */ = {isa = PBXFileReference; path = "graphic-card.svg"; sourceTree = "<group>"; };
24ADD22F5CE30B27020B479B /* MenuBarIcon.svg */ = {isa = PBXFileReference; path = MenuBarIcon.svg; sourceTree = "<group>"; };
41C778876BEF3D8B04F7C3C9 /* LaunchAtLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAtLogin.swift; sourceTree = "<group>"; };
@@ -179,6 +181,7 @@
45BD2DF1B775C733C8F75635 /* SettingsView.swift */,
8E43384BA603C866D148D9D8 /* SettingsWindowController.swift */,
8BCDFD427ACD83B8AA7BA39E /* sound-waves.svg */,
19F96370F33A525437F6E691 /* thunder.svg */,
16F2B1B9A74594526F34F5C9 /* WidgetOptionsView.swift */,
);
path = Onyx;
@@ -295,6 +298,7 @@
3991CB2BF41C6C014EF4528F /* chip.svg in Resources */,
F2A3AD184864422EA14A8C64 /* graphic-card.svg in Resources */,
B0FCCD92AF22F1636482EACD /* sound-waves.svg in Resources */,
A89903046D47175EE68392FA /* thunder.svg in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

27
Onyx/thunder.svg Normal file
View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
version="1.1"
id="svg1281"
xml:space="preserve"
width="682.66669"
height="682.66669"
viewBox="0 0 682.66669 682.66669"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs1285"><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath1295"><path
d="M 0,512 H 512 V 0 H 0 Z"
id="path1293" /></clipPath></defs><g
id="g1287"
transform="matrix(1.3333333,0,0,-1.3333333,0,682.66667)"><g
id="g1289"><g
id="g1291"
clip-path="url(#clipPath1295)"><g
id="g1297"
transform="translate(391,301)"><path
d="M 0,0 H -139.411 L -90,196 -270,-90 h 139.411 L -180,-286 Z"
style="fill:none;stroke:#000000;stroke-width:30;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path1299" /></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

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
}