Akku wird gezeichnet wie der von macOS

Der Blitz stand neben dem Akku statt darin, und das sah nach zwei Dingen
aus statt nach einem. Mit SF Symbols ging es nicht anders: fünf feste
Füllstufen, und Blitz-Varianten gibt es nur bei hundert Prozent.

Also selbst gezeichnet — ein Rahmen, ein Knubbel, eine Füllung, ein Blitz.
Das ist am Ende die kleinere Lösung als die Symbolakrobatik und gibt
obendrein, was vorher fehlte: die Füllung läuft stufenlos, 60 % sieht aus
wie 60 % und nicht wie 50 %.

Der Blitz wird ausgestanzt, nicht daraufgelegt. `destinationOut` nimmt weg,
was vorher gezeichnet wurde; übrig bleibt ein Loch in Blitzform, durch das
die Menüleiste scheint. Das funktioniert auf hellem wie dunklem Grund, ohne
die Hintergrundfarbe zu kennen — und genau so sieht der von macOS aus.

Unter zwanzig Prozent wird die Füllung rot, beim Laden nie: eine Warnung
vor etwas, das sich schon erledigt, ist keine.

Dieselbe Form auch in der Akku-Kachel. Zwei verschiedene Akkus in einer App
wären schlechter als gar keiner.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-08-11 15:49:04 +02:00
parent 4b5e86b3f4
commit 9968236a37
4 changed files with 261 additions and 36 deletions

View File

@@ -122,17 +122,14 @@ final class MetricStatusView: NSView, WidthReporting {
/// jedes Prozent alle Symbole rechts davon hin und her.
private var width: CGFloat {
switch presentation {
case .symbol: max(MenuBarText.minimumWidth,
MenuBarText.symbolWidth(currentSymbol, height: 15) + 4)
case .symbol: max(MenuBarText.minimumWidth, symbolSlot + 4)
case .graph: 28
case .bars: barsWidth
case .value: MenuBarText.width(for: reference)
case .valueAndGraph: MenuBarText.width(for: reference) + 2 + 28
case .labelAndValue: MenuBarText.width(for: "\(label) \(reference)")
case .symbolAndValue:
MenuBarText.width(for: reference)
+ MenuBarText.symbolWidth(currentSymbol, height: 13)
+ MenuBarText.innerGap
MenuBarText.width(for: reference) + symbolSlot + MenuBarText.innerGap
}
}
@@ -200,10 +197,15 @@ final class MetricStatusView: NSView, WidthReporting {
drawText("\(label) \(displayValue)", color: color)
case .symbolAndValue:
let glyph = MenuBarText.symbolWidth(currentSymbol, height: 13)
let symbolRect = NSRect(x: 0, y: bounds.midY - 6.5, width: glyph, height: 13)
MenuBarText.drawSymbol(currentSymbol, color: color, in: symbolRect)
drawChargingBolt(color: color, over: symbolRect)
let glyph = symbolSlot
if metric == .battery {
drawBattery(color: color,
at: NSPoint(x: 0, y: bounds.midY - BatteryGlyph.size.height / 2))
} else {
MenuBarText.drawSymbol(currentSymbol, color: color,
in: NSRect(x: 0, y: bounds.midY - 6.5,
width: glyph, height: 13))
}
drawText(displayValue, color: color,
in: NSRect(x: glyph + MenuBarText.innerGap, y: 0,
width: bounds.width - glyph - MenuBarText.innerGap,
@@ -224,12 +226,39 @@ final class MetricStatusView: NSView, WidthReporting {
}
private func drawSymbol(color: NSColor) {
if metric == .battery {
drawBattery(color: color,
at: NSPoint(x: bounds.midX - BatteryGlyph.size.width / 2,
y: bounds.midY - BatteryGlyph.size.height / 2))
return
}
let side: CGFloat = 15
let width = MenuBarText.symbolWidth(currentSymbol, height: side)
let rect = NSRect(x: bounds.midX - width / 2, y: bounds.midY - side / 2,
width: width, height: side)
MenuBarText.drawSymbol(currentSymbol, color: color, in: rect)
drawChargingBolt(color: color, over: rect)
MenuBarText.drawSymbol(currentSymbol, color: color,
in: NSRect(x: bounds.midX - width / 2,
y: bounds.midY - side / 2,
width: width, height: side))
}
/// Der Akku wird selbst gezeichnet mit stufenloser Füllung und dem Blitz
/// mitten drin, so wie macOS es zeigt.
private func drawBattery(color: NSColor, at origin: NSPoint) {
guard let battery = snapshot.battery else {
MenuBarText.drawSymbol(BatterySymbol.unavailable, color: color,
in: NSRect(x: origin.x, y: origin.y,
width: 15, height: 15))
return
}
BatteryGlyph.draw(charge: battery.charge, isCharging: battery.isCharging,
color: color,
in: CGRect(origin: origin, size: BatteryGlyph.size))
}
/// Wie breit das Glyph ist. Der Akku wird selbst gezeichnet und hat eigene
/// Maße; alles andere ist ein SF-Symbol.
private var symbolSlot: CGFloat {
metric == .battery ? BatteryGlyph.size.width
: MenuBarText.symbolWidth(currentSymbol, height: 13)
}
/// Das Symbol zum **Zustand**, nicht zur Metrik.
@@ -244,17 +273,6 @@ final class MetricStatusView: NSView, WidthReporting {
return BatterySymbol.name(charge: battery.charge)
}
/// Beim Laden ein kleiner Blitz über dem Füllstand. Zwei Zeichnungen statt
/// eines Symbols, weil es die kombinierten Glyphen nur bei 100 % gibt.
private func drawChargingBolt(color: NSColor, over rect: NSRect) {
guard metric == .battery, snapshot.battery?.isCharging == true else { return }
let side: CGFloat = 8
MenuBarText.drawSymbol(BatterySymbol.chargingBolt, color: color,
in: NSRect(x: rect.maxX - side * 0.75,
y: rect.maxY - side * 0.7,
width: side, height: side))
}
private func drawGraph(color: NSColor, in rect: NSRect) {
guard history.count > 1, let context = NSGraphicsContext.current?.cgContext else { return }
let step = rect.width / CGFloat(history.count - 1)