From bcabd48fa4bd4cdb0ed625f81fed6ca490380a81 Mon Sep 17 00:00:00 2001 From: Serhiy Mytrovtsiy Date: Fri, 23 Aug 2024 20:34:10 +0200 Subject: [PATCH] feat: added Mini widget to the Sensors module. It has quite a lot of limitations and only one sensor could be visualized (I hope understandable why) (#2029) --- Kit/Widgets/Mini.swift | 13 ++++++- Kit/helpers.swift | 23 +++++++++-- Modules/Sensors/config.plist | 22 ++++++++++- Modules/Sensors/main.swift | 71 ++++++++++++++++++++++------------ Modules/Sensors/settings.swift | 46 +++++++++++++++++----- Modules/Sensors/values.swift | 18 +++++++++ Stats/Views/AppSettings.swift | 12 +++--- 7 files changed, 159 insertions(+), 46 deletions(-) diff --git a/Kit/Widgets/Mini.swift b/Kit/Widgets/Mini.swift index 2e911375..3a0b9844 100644 --- a/Kit/Widgets/Mini.swift +++ b/Kit/Widgets/Mini.swift @@ -21,6 +21,7 @@ public class Mini: WidgetWrapper { private var _value: Double = 0 private var _pressureLevel: DispatchSource.MemoryPressureEvent = .normal private var _colorZones: colorZones = (0.6, 0.8) + private var _suffix: String = "%" private var defaultLabel: String private var _label: String @@ -95,11 +96,13 @@ public class Mini: WidgetWrapper { var pressureLevel: DispatchSource.MemoryPressureEvent = .normal var colorZones: colorZones = (0.6, 0.8) var label: String = "" + var suffix: String = "" self.queue.sync { value = self._value pressureLevel = self._pressureLevel colorZones = self._colorZones label = self._label + suffix = self._suffix } let valueSize: CGFloat = self.labelState ? 12 : 14 @@ -138,7 +141,7 @@ public class Mini: WidgetWrapper { NSAttributedString.Key.paragraphStyle: style ] let rect = CGRect(x: origin.x, y: origin.y, width: self.width - (Constants.Widget.margin.x*2), height: valueSize+1) - let str = NSAttributedString.init(string: "\(Int(value.rounded(toPlaces: 2) * 100))%", attributes: stringAttributes) + let str = NSAttributedString.init(string: "\(Int(value.rounded(toPlaces: 2) * 100))\(suffix)", attributes: stringAttributes) str.draw(with: rect) self.setWidth(width) @@ -180,6 +183,14 @@ public class Mini: WidgetWrapper { }) } + public func setSuffix(_ newSuffix: String) { + guard self._suffix != newSuffix else { return } + self._suffix = newSuffix + DispatchQueue.main.async(execute: { + self.display() + }) + } + // MARK: - Settings public override func settings() -> NSView { diff --git a/Kit/helpers.swift b/Kit/helpers.swift index c16c27fc..60f4c6a9 100644 --- a/Kit/helpers.swift +++ b/Kit/helpers.swift @@ -1275,7 +1275,7 @@ public class PreferencesSection: NSStackView { } public func delete(_ id: String) { - let views = self.container.subviews.filter({ $0.identifier != nil }) + let views = self.container.subviews views.enumerated().forEach { (i, v) in guard v.identifier?.rawValue == id else { return } if self.container.subviews.indices.contains(i-1) { @@ -1286,19 +1286,31 @@ public class PreferencesSection: NSStackView { } v.removeFromSuperview() } - return } public func contains(_ id: String) -> Bool { self.container.subviews.contains(where: { $0.identifier?.rawValue == id }) } - public func toggleVisibility(_ at: Int, newState: Bool) { + public func setRowVisibility(_ at: Int, newState: Bool) { for i in self.container.subviews.indices where i/2 == at && Double(i).remainder(dividingBy: 2) == 0 { self.container.subviews[i-1].isHidden = !newState self.container.subviews[i].isHidden = !newState } } + public func setRowVisibility(_ id: String, newState: Bool) { + guard let at = self.container.subviews.firstIndex(where: { $0.identifier?.rawValue == id }) else { return } + self.setRowVisibility(at/2, newState: newState) + } + public func setRowVisibility(_ row: PreferencesRow, newState: Bool) { + guard let at = self.container.subviews.firstIndex(where: { $0 == row }) else { return } + self.setRowVisibility(at/2, newState: newState) + } + + public func findRow(_ id: String) -> PreferencesRow? { + let rows: [PreferencesRow] = self.container.subviews.filter({ $0 is PreferencesRow }).compactMap({ $0 as? PreferencesRow }) + return rows.first(where: { $0.identifier?.rawValue == id }) + } } private class PreferencesSeparator: NSView { @@ -1341,6 +1353,11 @@ public class PreferencesRow: NSStackView { fatalError("init(coder:) has not been implemented") } + public func replaceComponent(with view: NSView) { + self.subviews.removeLast() + self.addArrangedSubview(view) + } + private func text(_ title: String? = nil, _ description: String? = nil) -> NSView { let view: NSStackView = NSStackView() view.orientation = .vertical diff --git a/Modules/Sensors/config.plist b/Modules/Sensors/config.plist index 4b4de803..d09483bd 100644 --- a/Modules/Sensors/config.plist +++ b/Modules/Sensors/config.plist @@ -19,6 +19,24 @@ Order 0 + mini + + Default + + Title + Sensor + Preview + + Value + 0.12 + + Unsupported colors + + pressure + + Order + 1 + sensors Default @@ -29,7 +47,7 @@ 38°,41° Order - 1 + 2 bar_chart @@ -48,7 +66,7 @@ cluster Order - 2 + 3 Settings diff --git a/Modules/Sensors/main.swift b/Modules/Sensors/main.swift index fc35fc26..9330bd7a 100644 --- a/Modules/Sensors/main.swift +++ b/Modules/Sensors/main.swift @@ -23,11 +23,14 @@ public class Sensors: Module { FanValue(rawValue: Store.shared.string(key: "\(self.config.name)_fanValue", defaultValue: "percentage")) ?? .percentage } + private var selectedSensor: String + public init() { self.settingsView = Settings(.sensors) self.popupView = Popup() self.portalView = Portal(.sensors) self.notificationsView = Notifications(.sensors) + self.selectedSensor = Store.shared.string(key: "\(ModuleType.sensors.rawValue)_sensor", defaultValue: "Average System Total") super.init( popup: self.popupView, @@ -74,6 +77,11 @@ public class Sensors: Module { } } } + self.selectedSensor = Store.shared.string(key: "\(ModuleType.sensors.rawValue)_sensor", defaultValue: self.selectedSensor) + self.settingsView.selectedHandler = { [weak self] value in + self?.selectedSensor = value + self?.sensorsReader?.read() + } self.setReaders([self.sensorsReader]) } @@ -90,40 +98,53 @@ public class Sensors: Module { } } - private func checkIfNoSensorsEnabled() { - guard let reader = self.sensorsReader else { return } - if reader.list.sensors.filter({ $0.state }).isEmpty { - NotificationCenter.default.post(name: .toggleModule, object: nil, userInfo: ["module": self.config.name, "state": false]) - } - } - private func usageCallback(_ raw: Sensors_List?) { guard let value = raw, self.enabled else { return } - var list: [Stack_t] = [] - var flatList: [[ColorValue]] = [] - - value.sensors.forEach { (s: Sensor_p) in - if s.state { - var value = s.formattedMiniValue - if let f = s as? Fan { - flatList.append([ColorValue(((f.value*100)/f.maxSpeed)/100)]) - if self.fanValueState == .percentage { - value = "\(f.percentage)%" - } - } - list.append(Stack_t(key: s.key, value: value)) - } - } - self.popupView.usageCallback(value.sensors) self.portalView.usageCallback(value.sensors) self.notificationsView.usageCallback(value.sensors) self.menuBar.widgets.filter{ $0.isActive }.forEach { (w: SWidget) in switch w.item { - case let widget as StackWidget: widget.setValues(list) - case let widget as BarChart: widget.setValue(flatList) + case let widget as Mini: + if let active = value.sensors.first(where: { $0.key == self.selectedSensor }) { + var value: Double = active.value/100 + var unit: String = active.miniUnit + if let fan = active as? Fan, self.fanValueState == .percentage { + value = Double(fan.percentage)/100 + unit = "%" + } + if value > 999 { + unit = "" + } + widget.setValue(value) + widget.setSuffix(unit) + } + case let widget as StackWidget: + var list: [Stack_t] = [] + + value.sensors.forEach { (s: Sensor_p) in + if s.state { + var value = s.formattedMiniValue + if let f = s as? Fan { + if self.fanValueState == .percentage { + value = "\(f.percentage)%" + } + } + list.append(Stack_t(key: s.key, value: value)) + } + } + + widget.setValues(list) + case let widget as BarChart: + var flatList: [[ColorValue]] = [] + value.sensors.filter{ $0 is Fan }.forEach { (s: Sensor_p) in + if s.state, let f = s as? Fan { + flatList.append([ColorValue(((f.value*100)/f.maxSpeed)/100)]) + } + } + widget.setValue(flatList) default: break } } diff --git a/Modules/Sensors/settings.swift b/Modules/Sensors/settings.swift index f1bc1dee..4834451c 100644 --- a/Modules/Sensors/settings.swift +++ b/Modules/Sensors/settings.swift @@ -20,14 +20,17 @@ internal class Settings: NSStackView, Settings_v { private var unknownSensorsState: Bool = false private var fanValueState: FanValue = .percentage - private let title: String - private var button: NSPopUpButton? - private var list: [Sensor_p] = [] - private var widgets: [widget_t] = [] public var callback: (() -> Void) = {} public var HIDcallback: (() -> Void) = {} public var unknownCallback: (() -> Void) = {} public var setInterval: ((_ value: Int) -> Void) = {_ in } + public var selectedHandler: (String) -> Void = {_ in } + + private let title: String + private var button: NSPopUpButton? + private var list: [Sensor_p] = [] + private var sensorsPrefs: PreferencesSection? + private var selectedSensor: String = "Average System Total" public init(_ module: ModuleType) { self.title = module.rawValue @@ -43,6 +46,7 @@ internal class Settings: NSStackView, Settings_v { self.fansSyncState = Store.shared.bool(key: "\(self.title)_fansSync", defaultValue: self.fansSyncState) self.unknownSensorsState = Store.shared.bool(key: "\(self.title)_unknown", defaultValue: self.unknownSensorsState) self.fanValueState = FanValue(rawValue: Store.shared.string(key: "\(self.title)_fanValue", defaultValue: self.fanValueState.rawValue)) ?? .percentage + self.selectedSensor = Store.shared.string(key: "\(self.title)_sensor", defaultValue: self.selectedSensor) self.addArrangedSubview(PreferencesSection([ PreferencesRow(localizedString("Update interval"), component: selectView( @@ -68,19 +72,26 @@ internal class Settings: NSStackView, Settings_v { )) ])) - var sensorsPrefs: [PreferencesRow] = [ + var sensorsRows: [PreferencesRow] = [ PreferencesRow(localizedString("Show unknown sensors"), component: switchView( action: #selector(self.toggleuUnknownSensors), state: self.unknownSensorsState )) ] if isARM { - sensorsPrefs.append(PreferencesRow(localizedString("HID sensors"), component: switchView( + sensorsRows.append(PreferencesRow(localizedString("HID sensors"), component: switchView( action: #selector(self.toggleHID), state: self.hidState ))) } - self.addArrangedSubview(PreferencesSection(sensorsPrefs)) + sensorsRows.append(PreferencesRow(localizedString("Sensor to show"), id: "active_sensor", component: selectView( + action: #selector(self.handleSelection), + items: [], + selected: self.selectedSensor) + )) + let sensorsPrefs = PreferencesSection(sensorsRows) + self.sensorsPrefs = sensorsPrefs + self.addArrangedSubview(sensorsPrefs) } required init?(coder: NSCoder) { @@ -107,6 +118,7 @@ internal class Settings: NSStackView, Settings_v { } } + var buttonList: [KeyValue_t] = [] types.forEach { (typ: SensorType) in let section = PreferencesSection(label: typ.rawValue) section.identifier = NSUserInterfaceItemIdentifier("sensor") @@ -126,19 +138,29 @@ internal class Settings: NSStackView, Settings_v { ) btn.identifier = NSUserInterfaceItemIdentifier(rawValue: s.key) section.add(PreferencesRow(localizedString(s.name), component: btn)) + buttonList.append(KeyValue_t(key: s.key, value: "\(localizedString(typ.rawValue)) - \(s.name)")) } } self.addArrangedSubview(section) } - self.widgets = widgets + if let row = self.sensorsPrefs?.findRow("active_sensor") { + if !widgets.isEmpty { + self.sensorsPrefs?.setRowVisibility(row, newState: widgets.contains(where: { $0 == .mini })) + } + row.replaceComponent(with: selectView( + action: #selector(self.handleSelection), + items: buttonList, + selected: self.selectedSensor + )) + } } public func setList(_ list: [Sensor_p]?) { guard let list else { return } self.list = self.unknownSensorsState ? list : list.filter({ $0.group != .unknown }) - self.load(widgets: self.widgets) + self.load(widgets: []) } @objc private func toggleSensor(_ sender: NSControl) { @@ -179,4 +201,10 @@ internal class Settings: NSStackView, Settings_v { self.callback() } } + @objc private func handleSelection(_ sender: NSPopUpButton) { + guard let item = sender.selectedItem, let id = item.representedObject as? String else { return } + self.selectedSensor = id + Store.shared.set(key: "\(self.title)_sensor", value: self.selectedSensor) + self.selectedHandler(self.selectedSensor) + } } diff --git a/Modules/Sensors/values.swift b/Modules/Sensors/values.swift index 6c625913..53e57e36 100644 --- a/Modules/Sensors/values.swift +++ b/Modules/Sensors/values.swift @@ -46,6 +46,7 @@ public protocol Sensor_p { var localValue: Double { get } var unit: String { get } + var miniUnit: String { get } var formattedValue: String { get } var formattedMiniValue: String { get } var formattedPopupValue: String { get } @@ -152,6 +153,22 @@ public struct Sensor: Sensor_p, Codable { return "RPM" } } + public var miniUnit: String { + switch self.type { + case .temperature: + return "°" + case .voltage: + return "V" + case .power: + return "W" + case .energy: + return "Wh" + case .current: + return "A" + default: + return "" + } + } public var formattedValue: String { switch self.type { @@ -251,6 +268,7 @@ public struct Fan: Sensor_p, Codable { public var isComputed: Bool = false public var average: Bool = false public var unit: String = "RPM" + public var miniUnit: String = "" public var formattedValue: String { "\(Int(self.value)) RPM" diff --git a/Stats/Views/AppSettings.swift b/Stats/Views/AppSettings.swift index 6ba6298b..5c2e2382 100644 --- a/Stats/Views/AppSettings.swift +++ b/Stats/Views/AppSettings.swift @@ -131,9 +131,9 @@ class ApplicationSettings: NSStackView { )) ]) scrollView.stackView.addArrangedSubview(self.combinedModulesView!) - self.combinedModulesView?.toggleVisibility(1, newState: self.combinedModulesState) - self.combinedModulesView?.toggleVisibility(2, newState: self.combinedModulesState) - self.combinedModulesView?.toggleVisibility(3, newState: self.combinedModulesState) + self.combinedModulesView?.setRowVisibility(1, newState: self.combinedModulesState) + self.combinedModulesView?.setRowVisibility(2, newState: self.combinedModulesState) + self.combinedModulesView?.setRowVisibility(3, newState: self.combinedModulesState) scrollView.stackView.addArrangedSubview(PreferencesSection([ PreferencesRow( @@ -292,9 +292,9 @@ class ApplicationSettings: NSStackView { @objc private func toggleCombinedModules(_ sender: NSButton) { self.combinedModulesState = sender.state == NSControl.StateValue.on - self.combinedModulesView?.toggleVisibility(1, newState: self.combinedModulesState) - self.combinedModulesView?.toggleVisibility(2, newState: self.combinedModulesState) - self.combinedModulesView?.toggleVisibility(3, newState: self.combinedModulesState) + self.combinedModulesView?.setRowVisibility(1, newState: self.combinedModulesState) + self.combinedModulesView?.setRowVisibility(2, newState: self.combinedModulesState) + self.combinedModulesView?.setRowVisibility(3, newState: self.combinedModulesState) NotificationCenter.default.post(name: .toggleOneView, object: nil, userInfo: nil) }