Menüleiste: Breite mitwachsend, Symbole unverzerrt, Sensor wählbar
Die Netzwerkkachel war auf „↓ 999,9 MB/s" dimensioniert und stand damit neben „↓ 13 KB/s" zur Hälfte leer. Auf den schlimmsten Fall auszulegen ist für einen Wert, der sich um den Faktor tausend ändert, die falsche Antwort. Gemessen wird jetzt, was dasteht — mit unsymmetrischer Hysterese: sofort wachsen, denn abgeschnittene Messwerte sind falsche Messwerte, und erst nach zehn ruhigeren Messungen schrumpfen, damit ein Lastausschlag die Leiste nicht zum Pumpen bringt. Das Akkusymbol war zerquetscht. Ein Batteriesymbol ist doppelt so breit wie hoch und wurde in ein Quadrat gezeichnet. Symbole behalten jetzt ihr Seitenverhältnis, und die Breite des Elements richtet sich nach dem, was das Symbol tatsächlich braucht — sonst hätte ein breites Glyph dieselbe Spalte wie ein schmales. Bei den Sensoren stand stumm das Maximum über alle Punkte: eine Zahl, von der niemand weiß, woher sie kommt. Jetzt wählbar, und der gewählte Sensor gibt sein eigenes Kürzel — „Temp 46°" sagt weniger als „Heatpipe 46°". Voreinstellung bleibt der wärmste Punkt, weil das die häufigste Frage ist. Und die Lüfterkachel zeigte „2500" ohne Einheit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -56,13 +56,38 @@ public enum MenuBarText {
|
||||
/// in einer selbst gezeichneten Ansicht muss die Farbe in die
|
||||
/// Symbolkonfiguration.
|
||||
public static func drawSymbol(_ name: String, color: NSColor, in rect: NSRect) {
|
||||
guard let tinted = symbol(name, color: color, height: rect.height) else { return }
|
||||
// Seitenverhältnis wahren. Ein Batteriesymbol ist doppelt so breit wie
|
||||
// hoch; in ein Quadrat gezeichnet sieht es zerquetscht aus.
|
||||
let size = fittedSize(of: tinted, height: rect.height)
|
||||
tinted.draw(in: NSRect(x: rect.midX - size.width / 2,
|
||||
y: rect.midY - size.height / 2,
|
||||
width: size.width, height: size.height))
|
||||
}
|
||||
|
||||
/// Wie breit ein Symbol bei dieser Höhe tatsächlich ist.
|
||||
///
|
||||
/// Ohne diese Messung bekommt ein breites Symbol denselben Platz wie ein
|
||||
/// schmales — entweder gequetscht oder mit einer Lücke daneben.
|
||||
public static func symbolWidth(_ name: String, height: CGFloat) -> CGFloat {
|
||||
guard let image = symbol(name, color: .labelColor, height: height) else {
|
||||
return height
|
||||
}
|
||||
return fittedSize(of: image, height: height).width
|
||||
}
|
||||
|
||||
private static func symbol(_ name: String, color: NSColor, height: CGFloat) -> NSImage? {
|
||||
guard let image = NSImage(systemSymbolName: name, accessibilityDescription: nil)
|
||||
else { return }
|
||||
let configuration = NSImage.SymbolConfiguration(pointSize: rect.height,
|
||||
weight: .regular)
|
||||
else { return nil }
|
||||
let configuration = NSImage.SymbolConfiguration(pointSize: height, weight: .regular)
|
||||
.applying(NSImage.SymbolConfiguration(paletteColors: [color]))
|
||||
let tinted = image.withSymbolConfiguration(configuration) ?? image
|
||||
tinted.draw(in: rect)
|
||||
return image.withSymbolConfiguration(configuration) ?? image
|
||||
}
|
||||
|
||||
private static func fittedSize(of image: NSImage, height: CGFloat) -> NSSize {
|
||||
let natural = image.size
|
||||
guard natural.height > 0 else { return NSSize(width: height, height: height) }
|
||||
return NSSize(width: (natural.width / natural.height) * height, height: height)
|
||||
}
|
||||
|
||||
public static func draw(_ text: String, color: NSColor, in rect: NSRect) {
|
||||
@@ -83,3 +108,47 @@ public enum MenuBarText {
|
||||
public protocol WidthReporting: AnyObject {
|
||||
var onWidthChange: ((CGFloat) -> Void)? { get set }
|
||||
}
|
||||
|
||||
/// Eine Breite, die mitwächst — aber nur ungern schrumpft.
|
||||
///
|
||||
/// Eine feste Breite für den schlimmsten Fall lässt neben jedem gewöhnlichen
|
||||
/// Wert Platz stehen: „↓ 13 KB/s" braucht gut halb so viel wie „↓ 999,9 MB/s".
|
||||
/// Eine Breite, die jedem Messwert folgt, schiebt dagegen bei jeder Messung
|
||||
/// alle Symbole rechts davon hin und her.
|
||||
///
|
||||
/// Der Kompromiss ist unsymmetrisch, und das mit Absicht: **sofort wachsen**,
|
||||
/// denn abgeschnittene Messwerte sind falsche Messwerte. **Zögernd
|
||||
/// schrumpfen**, damit ein kurzer Lastausschlag die Leiste nicht zum Pumpen
|
||||
/// bringt.
|
||||
public struct AdaptiveWidth: Sendable {
|
||||
|
||||
/// Wie viele ruhigere Messungen es braucht, bevor Platz abgegeben wird.
|
||||
/// Bei zwei Sekunden Abtastung sind das gut zwanzig Sekunden Geduld.
|
||||
public static let shrinkAfter = 10
|
||||
|
||||
public let minimum: CGFloat
|
||||
private var current: CGFloat = 0
|
||||
private var calmSamples = 0
|
||||
|
||||
public init(minimum: CGFloat) {
|
||||
self.minimum = minimum
|
||||
}
|
||||
|
||||
public mutating func update(needed: CGFloat) -> CGFloat {
|
||||
let wanted = max(needed, minimum)
|
||||
|
||||
if wanted > current {
|
||||
current = wanted
|
||||
calmSamples = 0
|
||||
} else if wanted < current {
|
||||
calmSamples += 1
|
||||
if calmSamples >= Self.shrinkAfter {
|
||||
current = wanted
|
||||
calmSamples = 0
|
||||
}
|
||||
} else {
|
||||
calmSamples = 0
|
||||
}
|
||||
return current
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user