import AppKit import SwiftUI /// Wie ein Modul seinen Messwert in der Menüleiste zeichnet. public enum MenuBarPresentation: String, Codable, Sendable, CaseIterable, Identifiable { /// Nur die Zahl, z. B. `34 %` oder `↓ 2,4 MB/s`. case value /// Schmales Verlaufsdiagramm über die letzten Messungen. case graph /// Ein Balken je Cluster bzw. je Richtung. case bars case valueAndGraph /// Nur ein Glyph, nach Grenzwert eingefärbt. case symbol /// Kürzel und Wert, z. B. `CPU 34 %`. /// /// Fünf nackte Prozentzahlen nebeneinander sind unbrauchbar: man sieht /// Zahlen und weiß nicht, welche wozu gehört. case labelAndValue /// Glyph und Wert, z. B. 🔋 `34 %`. case symbolAndValue public var id: String { rawValue } } /// Die Menüleisten-Entsprechung zu einem Widget. /// /// Ein Modul liefert nur Darstellung. Die Daten kommen aus demselben Provider, /// den auch das Panel-Widget benutzt — es gibt keine zweite Messschleife. @MainActor public protocol MenuBarModule: AnyObject { var id: String { get } var displayName: String { get } /// Das Kürzel für die Menüleiste — kurz, sonst frisst es die Leiste auf. var shortLabel: String { get } /// Zeichnet den kompakten Zustand. Muss eine feste Breite je Darstellungsart /// liefern, sonst springt die ganze Menüleiste bei jedem Messwert. func makeStatusView(presentation: MenuBarPresentation) -> NSView /// Der Inhalt des Popovers beim Klick. func makePopoverView() -> AnyView /// Wird gerufen, wenn das Popover dieses Moduls auf- und wieder zugeht. /// /// **Die einzige verlässliche Stelle für alles, was nur laufen darf, /// solange jemand hinsieht.** SwiftUIs `onDisappear` bleibt bei einem /// `NSPopover` aus: dessen Controller lebt nach dem Schließen weiter, die /// View verschwindet nie aus der Hierarchie. Wer eine Messung daran hängt, /// hängt sie an etwas, das nie eintritt — im Fall des Mikrofons mit dem /// orangen Punkt als Beweis. func popoverDidOpen() func popoverDidClose() /// Wird gerufen, wenn das Modul sichtbar wird bzw. verschwindet — hier /// meldet sich das Modul beim Provider an und wieder ab. func activate() func deactivate() } /// Was der Nutzer je Modul eingestellt hat. public struct MenuBarModuleSettings: Codable, Sendable, Equatable { public var isEnabled: Bool public var presentation: MenuBarPresentation /// Abtastintervall in Sekunden, 1–10. public var refreshInterval: TimeInterval public var usesThresholdColors: Bool public init(isEnabled: Bool = false, presentation: MenuBarPresentation = .symbolAndValue, refreshInterval: TimeInterval = 2, usesThresholdColors: Bool = true) { self.isEnabled = isEnabled self.presentation = presentation self.refreshInterval = refreshInterval.clamped(to: 1...10) self.usesThresholdColors = usesThresholdColors } } extension TimeInterval { func clamped(to range: ClosedRange) -> TimeInterval { Swift.min(Swift.max(self, range.lowerBound), range.upperBound) } } public extension MenuBarModule { // Die meisten Module haben nichts abzuräumen. func popoverDidOpen() {} func popoverDidClose() {} }