Blitz blendet zwischen zwei fertigen Fassungen über

Deine Idee war die bessere. Eine helle Fläche mit dunklem Rand ließ sich aus
einem einzelnen Symbol nur nachbauen, indem dasselbe Zeichen zweimal in
verschiedener Größe übereinander gezeichnet wurde — und die
Größenverhältnisse sahen nie stimmig aus, wie man an den drei Anläufen
sieht. Zwei fertige Fassungen ineinander zu blenden ist der geradere Weg:
gezeichnet wird genau das, was in der Datei steht.

Erst die dunkle voll deckend, dann die helle mit steigender Deckkraft
darüber. Zeichnete man beide anteilig, schiene dazwischen der Hintergrund
durch und der Blitz wirkte blass.

Ohne eigenen Rand bekommt der Blitz den vollen Innenraum des Akkus statt
Innenraum minus Rand — er ist damit wieder etwas größer, bleibt aber
innerhalb des Rahmens. Der Test hält die neue Grenze fest.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-08-13 14:20:06 +02:00
parent 80b083259e
commit c990ea558b
6 changed files with 94 additions and 77 deletions

View File

@@ -16,7 +16,9 @@
378A5A417112CCB31EF3F774 /* OnyxNotch in Frameworks */ = {isa = PBXBuildFile; productRef = 3A32CAE6CC4D01D660F0016F /* OnyxNotch */; }; 378A5A417112CCB31EF3F774 /* OnyxNotch in Frameworks */ = {isa = PBXBuildFile; productRef = 3A32CAE6CC4D01D660F0016F /* OnyxNotch */; };
3991CB2BF41C6C014EF4528F /* chip.svg in Resources */ = {isa = PBXBuildFile; fileRef = 085D860E654E09E4CD6E535A /* chip.svg */; }; 3991CB2BF41C6C014EF4528F /* chip.svg in Resources */ = {isa = PBXBuildFile; fileRef = 085D860E654E09E4CD6E535A /* chip.svg */; };
39E29ADC71D4860543AD25EB /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45BD2DF1B775C733C8F75635 /* SettingsView.swift */; }; 39E29ADC71D4860543AD25EB /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45BD2DF1B775C733C8F75635 /* SettingsView.swift */; };
3C7D32A39B906BD7385B1231 /* bolt-light.svg in Resources */ = {isa = PBXBuildFile; fileRef = B8214C5599D1857E832447EC /* bolt-light.svg */; };
4E729C7A6498B4679C8D2C42 /* OnyxCore in Frameworks */ = {isa = PBXBuildFile; productRef = C64A111F449D0F9D36ED46FB /* OnyxCore */; }; 4E729C7A6498B4679C8D2C42 /* OnyxCore in Frameworks */ = {isa = PBXBuildFile; productRef = C64A111F449D0F9D36ED46FB /* OnyxCore */; };
574FDD46FEBAC96E38CDFC7A /* bolt-dark.svg in Resources */ = {isa = PBXBuildFile; fileRef = 7A866E72010907DF4BEF025C /* bolt-dark.svg */; };
5BE38CAFCDA4B9CBE787CABA /* CalendarProvider in Frameworks */ = {isa = PBXBuildFile; productRef = A3E3949D664131D593CBEEDC /* CalendarProvider */; }; 5BE38CAFCDA4B9CBE787CABA /* CalendarProvider in Frameworks */ = {isa = PBXBuildFile; productRef = A3E3949D664131D593CBEEDC /* CalendarProvider */; };
68EE5495435A82E20248EBC2 /* OnyxHelperProtocol in Frameworks */ = {isa = PBXBuildFile; productRef = 91C048471C023EC8A82B1BD3 /* OnyxHelperProtocol */; }; 68EE5495435A82E20248EBC2 /* OnyxHelperProtocol in Frameworks */ = {isa = PBXBuildFile; productRef = 91C048471C023EC8A82B1BD3 /* OnyxHelperProtocol */; };
6A972372414D6EFDC81E2661 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = B442E8A39C37C8FD92B4CA83 /* main.swift */; }; 6A972372414D6EFDC81E2661 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = B442E8A39C37C8FD92B4CA83 /* main.swift */; };
@@ -27,7 +29,6 @@
9C2E419014B7E04630FDF151 /* OnyxApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89CAC2A7DA89B8707D45D65 /* OnyxApp.swift */; }; 9C2E419014B7E04630FDF151 /* OnyxApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89CAC2A7DA89B8707D45D65 /* OnyxApp.swift */; };
9EC9D57A96489F5578000822 /* NetworkProvider in Frameworks */ = {isa = PBXBuildFile; productRef = FF4E99C8BF0D8944C314A8AF /* NetworkProvider */; }; 9EC9D57A96489F5578000822 /* NetworkProvider in Frameworks */ = {isa = PBXBuildFile; productRef = FF4E99C8BF0D8944C314A8AF /* NetworkProvider */; };
A529046C33E6B00C0E9508FF /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 508DECB5C05264E6130C373E /* Localizable.xcstrings */; }; 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 */; }; A9617230F602C5D52F63A7E2 /* ShelfProvider in Frameworks */ = {isa = PBXBuildFile; productRef = FA6C9F90DF47DC0DFB9ED55D /* ShelfProvider */; };
B0FCCD92AF22F1636482EACD /* sound-waves.svg in Resources */ = {isa = PBXBuildFile; fileRef = 8BCDFD427ACD83B8AA7BA39E /* sound-waves.svg */; }; B0FCCD92AF22F1636482EACD /* sound-waves.svg in Resources */ = {isa = PBXBuildFile; fileRef = 8BCDFD427ACD83B8AA7BA39E /* sound-waves.svg */; };
B309DD8966B5348250CEC6BD /* LaunchAtLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41C778876BEF3D8B04F7C3C9 /* LaunchAtLogin.swift */; }; B309DD8966B5348250CEC6BD /* LaunchAtLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41C778876BEF3D8B04F7C3C9 /* LaunchAtLogin.swift */; };
@@ -59,7 +60,6 @@
0804288DC4A8146DD9F3FC3E /* Onyx.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Onyx.app; sourceTree = BUILT_PRODUCTS_DIR; }; 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>"; }; 085D860E654E09E4CD6E535A /* chip.svg */ = {isa = PBXFileReference; path = chip.svg; sourceTree = "<group>"; };
16F2B1B9A74594526F34F5C9 /* WidgetOptionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetOptionsView.swift; 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>"; }; 1E6A1C208C7EA56971A055E1 /* graphic-card.svg */ = {isa = PBXFileReference; path = "graphic-card.svg"; sourceTree = "<group>"; };
24ADD22F5CE30B27020B479B /* MenuBarIcon.svg */ = {isa = PBXFileReference; path = MenuBarIcon.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>"; }; 41C778876BEF3D8B04F7C3C9 /* LaunchAtLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAtLogin.swift; sourceTree = "<group>"; };
@@ -70,6 +70,7 @@
5FCD3B382484AE4E9EF2F4B6 /* OnboardingWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingWindowController.swift; sourceTree = "<group>"; }; 5FCD3B382484AE4E9EF2F4B6 /* OnboardingWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingWindowController.swift; sourceTree = "<group>"; };
6115888EE7738877C9304138 /* MixerMenuBarModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MixerMenuBarModule.swift; sourceTree = "<group>"; }; 6115888EE7738877C9304138 /* MixerMenuBarModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MixerMenuBarModule.swift; sourceTree = "<group>"; };
63A5510538846C8427AEB831 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = "<group>"; }; 63A5510538846C8427AEB831 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = "<group>"; };
7A866E72010907DF4BEF025C /* bolt-dark.svg */ = {isa = PBXFileReference; path = "bolt-dark.svg"; sourceTree = "<group>"; };
821B742C211265914B6CC44B /* OnyxKit */ = {isa = PBXFileReference; lastKnownFileType = folder; name = OnyxKit; path = Packages/OnyxKit; sourceTree = SOURCE_ROOT; }; 821B742C211265914B6CC44B /* OnyxKit */ = {isa = PBXFileReference; lastKnownFileType = folder; name = OnyxKit; path = Packages/OnyxKit; sourceTree = SOURCE_ROOT; };
87597F09E971C14CF1E475DB /* Onyx.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Onyx.entitlements; sourceTree = "<group>"; }; 87597F09E971C14CF1E475DB /* Onyx.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Onyx.entitlements; sourceTree = "<group>"; };
89309D9F5722FA1B91CF2905 /* FanWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FanWidget.swift; sourceTree = "<group>"; }; 89309D9F5722FA1B91CF2905 /* FanWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FanWidget.swift; sourceTree = "<group>"; };
@@ -77,6 +78,7 @@
8BCDFD427ACD83B8AA7BA39E /* sound-waves.svg */ = {isa = PBXFileReference; path = "sound-waves.svg"; sourceTree = "<group>"; }; 8BCDFD427ACD83B8AA7BA39E /* sound-waves.svg */ = {isa = PBXFileReference; path = "sound-waves.svg"; sourceTree = "<group>"; };
8E43384BA603C866D148D9D8 /* SettingsWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsWindowController.swift; sourceTree = "<group>"; }; 8E43384BA603C866D148D9D8 /* SettingsWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsWindowController.swift; sourceTree = "<group>"; };
B442E8A39C37C8FD92B4CA83 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; }; B442E8A39C37C8FD92B4CA83 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
B8214C5599D1857E832447EC /* bolt-light.svg */ = {isa = PBXFileReference; path = "bolt-light.svg"; sourceTree = "<group>"; };
B83C4E3CB821F903F3977E88 /* AppModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppModel.swift; sourceTree = "<group>"; }; B83C4E3CB821F903F3977E88 /* AppModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppModel.swift; sourceTree = "<group>"; };
F12041E330231599C6540C21 /* FanSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FanSettingsView.swift; sourceTree = "<group>"; }; F12041E330231599C6540C21 /* FanSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FanSettingsView.swift; sourceTree = "<group>"; };
F89CAC2A7DA89B8707D45D65 /* OnyxApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnyxApp.swift; sourceTree = "<group>"; }; F89CAC2A7DA89B8707D45D65 /* OnyxApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnyxApp.swift; sourceTree = "<group>"; };
@@ -164,6 +166,8 @@
0FBA13B97C53F1D0D740A633 /* Resources */, 0FBA13B97C53F1D0D740A633 /* Resources */,
055263008CB3306A8D09E6D4 /* AppIcon.icon */, 055263008CB3306A8D09E6D4 /* AppIcon.icon */,
B83C4E3CB821F903F3977E88 /* AppModel.swift */, B83C4E3CB821F903F3977E88 /* AppModel.swift */,
7A866E72010907DF4BEF025C /* bolt-dark.svg */,
B8214C5599D1857E832447EC /* bolt-light.svg */,
085D860E654E09E4CD6E535A /* chip.svg */, 085D860E654E09E4CD6E535A /* chip.svg */,
FC3FDFD387188D36AF8287DE /* FanControl.swift */, FC3FDFD387188D36AF8287DE /* FanControl.swift */,
F12041E330231599C6540C21 /* FanSettingsView.swift */, F12041E330231599C6540C21 /* FanSettingsView.swift */,
@@ -181,7 +185,6 @@
45BD2DF1B775C733C8F75635 /* SettingsView.swift */, 45BD2DF1B775C733C8F75635 /* SettingsView.swift */,
8E43384BA603C866D148D9D8 /* SettingsWindowController.swift */, 8E43384BA603C866D148D9D8 /* SettingsWindowController.swift */,
8BCDFD427ACD83B8AA7BA39E /* sound-waves.svg */, 8BCDFD427ACD83B8AA7BA39E /* sound-waves.svg */,
19F96370F33A525437F6E691 /* thunder.svg */,
16F2B1B9A74594526F34F5C9 /* WidgetOptionsView.swift */, 16F2B1B9A74594526F34F5C9 /* WidgetOptionsView.swift */,
); );
path = Onyx; path = Onyx;
@@ -295,10 +298,11 @@
FF97F56CE1C734E59F1B40F4 /* AppIcon.icon in Resources */, FF97F56CE1C734E59F1B40F4 /* AppIcon.icon in Resources */,
A529046C33E6B00C0E9508FF /* Localizable.xcstrings in Resources */, A529046C33E6B00C0E9508FF /* Localizable.xcstrings in Resources */,
7CE2EDC6BC58678269BF27AD /* MenuBarIcon.svg in Resources */, 7CE2EDC6BC58678269BF27AD /* MenuBarIcon.svg in Resources */,
574FDD46FEBAC96E38CDFC7A /* bolt-dark.svg in Resources */,
3C7D32A39B906BD7385B1231 /* bolt-light.svg in Resources */,
3991CB2BF41C6C014EF4528F /* chip.svg in Resources */, 3991CB2BF41C6C014EF4528F /* chip.svg in Resources */,
F2A3AD184864422EA14A8C64 /* graphic-card.svg in Resources */, F2A3AD184864422EA14A8C64 /* graphic-card.svg in Resources */,
B0FCCD92AF22F1636482EACD /* sound-waves.svg in Resources */, B0FCCD92AF22F1636482EACD /* sound-waves.svg in Resources */,
A89903046D47175EE68392FA /* thunder.svg in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

40
Onyx/bolt-dark.svg Normal file
View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<g>
<g>
<polygon points="426.667,213.333 284.444,213.333 284.444,0 85.333,298.667 227.556,298.667 227.556,512 "/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 666 B

1
Onyx/bolt-light.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" x="0" y="0" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512" xml:space="preserve" class=""><g><path d="M426.667 213.333H284.444V0L85.333 298.667h142.223V512z" fill="#ffffff" opacity="1" data-original="#000000" class=""></path></g></svg>

After

Width:  |  Height:  |  Size: 373 B

View File

@@ -1,27 +0,0 @@
<?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:#000000;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>

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -68,16 +68,22 @@ public enum BatteryGlyph {
return (filled, empty) return (filled, empty)
} }
/// Der Blitz als eigene Vorlage. /// Der Blitz in zwei Fassungen, hell und dunkel.
/// ///
/// SF Symbols' `bolt.fill` ist eine massive Fläche; die mitgelieferte /// **Warum zwei Dateien statt einer eingefärbten:** eine helle Fläche mit
/// Kontur trifft die Form besser und bringt schon einen kräftigen Strich /// dunklem Rand ließ sich aus einem einzelnen Symbol nur nachbauen, indem
/// mit. Fehlt die Datei, bleibt das Systemsymbol. /// dasselbe Zeichen zweimal in verschiedener Größe übereinander gezeichnet
public static let chargingGlyph: NSImage? = { /// wurde und die Größenverhältnisse sahen nie stimmig aus. Zwei fertige
guard let url = Bundle.main.url(forResource: "thunder", withExtension: "svg") /// Fassungen ineinander zu blenden ist der geradere Weg: gezeichnet wird
/// genau das, was in der Datei steht.
public static let boltLight: NSImage? = load("bolt-light")
public static let boltDark: NSImage? = load("bolt-dark")
private static func load(_ name: String) -> NSImage? {
guard let url = Bundle.main.url(forResource: name, withExtension: "svg")
else { return nil } else { return nil }
return NSImage(contentsOf: url) return NSImage(contentsOf: url)
}() }
/// Wie dick der Rand um das Zeichen ist. /// Wie dick der Rand um das Zeichen ist.
public static let outlineWidth: CGFloat = 1 public static let outlineWidth: CGFloat = 1
@@ -95,12 +101,11 @@ public enum BatteryGlyph {
/// der Außenhöhe ausgeht, landet bei einem Blitz, der den ganzen Akku /// der Außenhöhe ausgeht, landet bei einem Blitz, der den ganzen Akku
/// ausfüllt und an den Rahmen stößt. /// ausfüllt und an den Rahmen stößt.
public static func glyphHeight(for indicator: Indicator, in height: CGFloat) -> CGFloat { public static func glyphHeight(for indicator: Indicator, in height: CGFloat) -> CGFloat {
let inner = height - 2 * fillInset let inner = max(height - 2 * fillInset, 1)
let usable = max(inner - 2 * outlineWidth, 1)
switch indicator { switch indicator {
// Das Pausenzeichen ist von Haus aus höher gebaut als der Blitz. // Das Pausenzeichen ist von Haus aus höher gebaut als der Blitz.
case .paused: return usable * 0.8 case .paused: return inner * 0.8
default: return usable default: return inner
} }
} }
@@ -206,25 +211,20 @@ public enum BatteryGlyph {
return return
} }
// Der Blitz: mit dunklem Rand, und fadend. // Der Blitz blendet zwischen den beiden Fassungen über.
// //
// Der Rand ist immer die Gegenfarbe zum Blitz selbst. Damit bleibt er // Erst die dunkle voll deckend, dann die helle mit steigender
// in **jedem** Moment des Fadens lesbar: über der hellen Füllung trägt // Deckkraft darüber. So ist das Zeichen in jedem Moment vollständig
// ihn der Rand, über dem leeren Teil der Blitz. // gedeckt zeichnete man beide anteilig, schiene dazwischen der
// Hintergrund durch und der Blitz wirkte blass.
let whiteness = boltWhiteness(phase: fadePhase) let whiteness = boltWhiteness(phase: fadePhase)
let inverseColor = NSColor(white: 1 - whiteness, alpha: 1) if let dark = boltDark, let light = boltLight {
let boltColor = NSColor(white: whiteness, alpha: 1) dark.draw(in: glyphRect, from: .zero, operation: .sourceOver, fraction: 1)
light.draw(in: glyphRect, from: .zero, operation: .sourceOver,
if let custom = chargingGlyph { fraction: whiteness)
MenuBarText.drawTemplate(custom, color: inverseColor, in: outlineRect(glyphRect))
MenuBarText.drawTemplate(custom, color: boltColor, in: glyphRect)
return return
} }
if let outline = tint(glyph, with: inverseColor) { if let filled = tint(glyph, with: NSColor(white: whiteness, alpha: 1)) {
outline.draw(in: outlineRect(glyphRect), from: .zero,
operation: .sourceOver, fraction: 1)
}
if let filled = tint(glyph, with: boltColor) {
filled.draw(in: glyphRect, from: .zero, operation: .sourceOver, fraction: 1) filled.draw(in: glyphRect, from: .zero, operation: .sourceOver, fraction: 1)
} }
} }
@@ -285,12 +285,7 @@ public struct BatteryGlyphView: View {
let phase = context.date.timeIntervalSinceReferenceDate let phase = context.date.timeIntervalSinceReferenceDate
/ BatteryGlyph.fadeDuration / BatteryGlyph.fadeDuration
let whiteness = BatteryGlyph.boltWhiteness(phase: phase) let whiteness = BatteryGlyph.boltWhiteness(phase: phase)
ZStack { bolt(side: size, whiteness: whiteness)
bolt(name: name, side: size + 2 * BatteryGlyph.outlineWidth)
.foregroundStyle(Color(white: 1 - whiteness))
bolt(name: name, side: size)
.foregroundStyle(Color(white: whiteness))
}
} }
} else { } else {
Image(systemName: name) Image(systemName: name)
@@ -308,17 +303,20 @@ public struct BatteryGlyphView: View {
.frame(width: width, height: height) .frame(width: width, height: height)
} }
/// Dieselbe Vorlage wie in der Menüleiste, sonst das Systemsymbol. /// Dieselben zwei Fassungen wie in der Menüleiste, ineinander geblendet.
@ViewBuilder @ViewBuilder
private func bolt(name: String, side: CGFloat) -> some View { private func bolt(side: CGFloat, whiteness: Double) -> some View {
if let custom = BatteryGlyph.chargingGlyph { if let dark = BatteryGlyph.boltDark, let light = BatteryGlyph.boltLight {
Image(nsImage: custom) ZStack {
.renderingMode(.template) Image(nsImage: dark).resizable().aspectRatio(contentMode: .fit)
.resizable() Image(nsImage: light).resizable().aspectRatio(contentMode: .fit)
.aspectRatio(contentMode: .fit) .opacity(whiteness)
.frame(width: side, height: side) }
.frame(width: side, height: side)
} else { } else {
Image(systemName: name).font(.system(size: side)) Image(systemName: "bolt.fill")
.font(.system(size: side))
.foregroundStyle(Color(white: whiteness))
} }
} }

View File

@@ -237,14 +237,15 @@ struct BatteryGlyphSizeTests {
private let height = BatteryGlyph.size.height private let height = BatteryGlyph.size.height
@Test("Das Zeichen passt samt Rand in den Innenraum") @Test("Das Zeichen passt in den Innenraum")
func glyphAndOutlineFitInside() { func glyphFitsInside() {
// Der Fehler, um den es ging: von der Außenhöhe ausgegangen, füllte der // Der Fehler, um den es ging: von der Außenhöhe ausgegangen, füllte der
// Blitz den ganzen Akku und stieß an den Rahmen. // Blitz den ganzen Akku und stieß an den Rahmen. Bezugsgröße ist der
// Innenraum der Rahmen nimmt oben und unten Platz, die Füllung sitzt
// eingerückt.
let inner = height - 2 * BatteryGlyph.fillInset let inner = height - 2 * BatteryGlyph.fillInset
for indicator in BatteryGlyph.Indicator.allCases { for indicator in BatteryGlyph.Indicator.allCases {
let glyph = BatteryGlyph.glyphHeight(for: indicator, in: height) #expect(BatteryGlyph.glyphHeight(for: indicator, in: height) <= inner + 0.001)
#expect(glyph + 2 * BatteryGlyph.outlineWidth <= inner + 0.001)
} }
} }