Phase 5: Hardware- und Netzwerkmonitor, Panel und Menüleiste
Sechs neue Widgets (CPU, GPU, Speicher, Akku, Sensoren, Netzwerk) und dieselben sechs als einzeln aktivierbare Menüleisten-Module mit fünf Darstellungsarten. Panel-Widget und Menüleisten-Modul derselben Größe teilen sich die Messschleife über Referenzzählung. Ohne das liefe für jede Anzeige ein eigener Timer mit denselben IOKit- und SMC-Abfragen — bei sechs Modulen und ebenso vielen Widgets ein Vielfaches der nötigen Arbeit. Meldet sich der letzte Konsument ab, hört die Schleife auf; im Ruhezustand misst Onyx nichts. Die Differenzrechnung ist testgetrieben, und die Tests haben zwei echte Fehler gefunden. Erstens: mein Überlaufschutz beim Durchsatz hielt einen zurückgesetzten Zähler (Interface-Wechsel) für einen Überlauf und zeigte 4,3 GB/s. Da if_data64 64-Bit-Zähler liefert, die bei 10 Gbit/s erst nach 470 Jahren umlaufen, ist ein kleinerer Wert immer ein Zurücksetzen — die Unterscheidung produzierte nur den Ausreißer, den sie verhindern sollte. Zweitens: ByteCountFormatter schrieb bei null „Zero KB/s" statt „0 KB/s". Bei den CPU-Zählern bleibt der Überlaufschutz nötig: die sind 32 Bit breit und laufen nach gut 400 Tagen wirklich um, deshalb `&-` statt `-`. Sensoren kommen über den SMC-Leser aus Spike A statt über die private IOHID-Schnittstelle — auf dieser Maschine nachweislich verifiziert, und die dort gefundene Little-Endian-Eigenheit ist berücksichtigt. Nur eine kurze Liste benannter Fühler statt aller 3486 Keys: eine Wand aus Kürzeln ist keine Information. Gegengemessen statt vermutet: 15 Kerne, 39,8 W, GPU 50 %, 16,6/24 GB mit kritischem Druck, Akku 68 Zyklen bei 100 % Gesundheit, fünf Sensoren, zwei Lüfter, en0 mit 661 KB/s. Menüleisten-Module zeichnen in labelColor statt in den Onyx-Farben — nur die Systemfarbe passt sich heller wie dunkler Leiste an. Feste Breite je Darstellungsart, sonst schiebt jeder Messwert die halbe Leiste hin und her. Standardmäßig ist kein Modul aktiv: sechs neue Symbole beim ersten Start wären eine Zumutung. 145 Tests grün.
This commit is contained in:
@@ -3,10 +3,11 @@ import OnyxDesign
|
||||
import OnyxNotch
|
||||
import OnyxWidgetKit
|
||||
import CalendarProvider
|
||||
import OnyxMenuBar
|
||||
import WeatherProvider
|
||||
|
||||
enum SettingsTab: Hashable {
|
||||
case widgets, display, permissions
|
||||
case widgets, display, menubar, permissions
|
||||
}
|
||||
|
||||
struct SettingsView: View {
|
||||
@@ -23,6 +24,9 @@ struct SettingsView: View {
|
||||
DisplaySettings(model: model)
|
||||
.tabItem { Label("settings.tab.display", systemImage: "macbook") }
|
||||
.tag(SettingsTab.display)
|
||||
MenuBarSettings(model: model)
|
||||
.tabItem { Label("settings.tab.menubar", systemImage: "menubar.rectangle") }
|
||||
.tag(SettingsTab.menubar)
|
||||
PermissionSettings(calendarModel: calendarModel, weatherModel: weatherModel)
|
||||
.tabItem { Label("settings.tab.permissions", systemImage: "hand.raised") }
|
||||
.tag(SettingsTab.permissions)
|
||||
@@ -147,6 +151,99 @@ private struct PermissionRow: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Menüleiste
|
||||
|
||||
/// Jedes Modul einzeln an- und abschaltbar, mit eigener Darstellungsart.
|
||||
///
|
||||
/// Standardmäßig ist keines an: sechs neue Symbole beim ersten Start wären eine
|
||||
/// Zumutung, und die Menüleiste ist ohnehin knapp — auf einem Notch-Display
|
||||
/// blockiert die Kamera zusätzlich die Mitte.
|
||||
private struct MenuBarSettings: View {
|
||||
@Bindable var model: AppModel
|
||||
|
||||
private static let modules: [(id: String, label: LocalizedStringKey, symbol: String)] = [
|
||||
("metric.cpu", "metric.cpu", "cpu"),
|
||||
("metric.gpu", "metric.gpu", "cpu.fill"),
|
||||
("metric.memory", "metric.memory", "memorychip"),
|
||||
("metric.battery", "metric.battery", "battery.100"),
|
||||
("metric.temperature", "metric.sensors", "thermometer"),
|
||||
("network", "network.name", "network"),
|
||||
]
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("settings.menubar.hint")
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
List {
|
||||
ForEach(Self.modules, id: \.id) { module in
|
||||
ModuleRow(model: model, id: module.id,
|
||||
label: module.label, symbol: module.symbol)
|
||||
}
|
||||
}
|
||||
.listStyle(.inset)
|
||||
}
|
||||
.padding()
|
||||
// Der Zähler zwingt die Liste zum Neuzeichnen, wenn sich ein Modul
|
||||
// ändert — sonst müsste jede Zeile ihren eigenen Zustand beobachten.
|
||||
.id(model.menuBarRevision)
|
||||
}
|
||||
}
|
||||
|
||||
private struct ModuleRow: View {
|
||||
@Bindable var model: AppModel
|
||||
let id: String
|
||||
let label: LocalizedStringKey
|
||||
let symbol: String
|
||||
|
||||
var body: some View {
|
||||
let settings = model.menuBarSettings(for: id)
|
||||
|
||||
HStack {
|
||||
Toggle(isOn: Binding(
|
||||
get: { settings.isEnabled },
|
||||
set: { enabled in
|
||||
var updated = settings
|
||||
updated.isEnabled = enabled
|
||||
model.setMenuBarSettings(updated, for: id)
|
||||
})) {
|
||||
Label(label, systemImage: symbol)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Picker("", selection: Binding(
|
||||
get: { settings.presentation },
|
||||
set: { presentation in
|
||||
var updated = settings
|
||||
updated.presentation = presentation
|
||||
model.setMenuBarSettings(updated, for: id)
|
||||
})) {
|
||||
ForEach(MenuBarPresentation.allCases) { presentation in
|
||||
Text(presentation.localizedName).tag(presentation)
|
||||
}
|
||||
}
|
||||
.labelsHidden()
|
||||
.frame(width: 150)
|
||||
.disabled(!settings.isEnabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MenuBarPresentation {
|
||||
var localizedName: LocalizedStringKey {
|
||||
switch self {
|
||||
case .value: "settings.menubar.value"
|
||||
case .graph: "settings.menubar.graph"
|
||||
case .bars: "settings.menubar.bars"
|
||||
case .valueAndGraph: "settings.menubar.valueAndGraph"
|
||||
case .symbol: "settings.menubar.symbol"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Widgets
|
||||
|
||||
private struct WidgetSettings: View {
|
||||
|
||||
Reference in New Issue
Block a user