diff --git a/ModuleKit/Supporting Files/Assets.xcassets/widget_settings.imageset/Contents.json b/ModuleKit/Supporting Files/Assets.xcassets/widget_settings.imageset/Contents.json new file mode 100644 index 00000000..c0b2e18d --- /dev/null +++ b/ModuleKit/Supporting Files/Assets.xcassets/widget_settings.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "baseline_settings_white_18pt_1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "baseline_settings_white_18pt_2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "baseline_settings_white_18pt_3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/ModuleKit/Supporting Files/Assets.xcassets/widget_settings.imageset/baseline_settings_white_18pt_1x.png b/ModuleKit/Supporting Files/Assets.xcassets/widget_settings.imageset/baseline_settings_white_18pt_1x.png new file mode 100644 index 00000000..585d0669 Binary files /dev/null and b/ModuleKit/Supporting Files/Assets.xcassets/widget_settings.imageset/baseline_settings_white_18pt_1x.png differ diff --git a/ModuleKit/Supporting Files/Assets.xcassets/widget_settings.imageset/baseline_settings_white_18pt_2x.png b/ModuleKit/Supporting Files/Assets.xcassets/widget_settings.imageset/baseline_settings_white_18pt_2x.png new file mode 100644 index 00000000..19dd82a9 Binary files /dev/null and b/ModuleKit/Supporting Files/Assets.xcassets/widget_settings.imageset/baseline_settings_white_18pt_2x.png differ diff --git a/ModuleKit/Supporting Files/Assets.xcassets/widget_settings.imageset/baseline_settings_white_18pt_3x.png b/ModuleKit/Supporting Files/Assets.xcassets/widget_settings.imageset/baseline_settings_white_18pt_3x.png new file mode 100644 index 00000000..53c98a0a Binary files /dev/null and b/ModuleKit/Supporting Files/Assets.xcassets/widget_settings.imageset/baseline_settings_white_18pt_3x.png differ diff --git a/ModuleKit/Widgets/BarChart.swift b/ModuleKit/Widgets/BarChart.swift index 0bfa783b..e807250a 100644 --- a/ModuleKit/Widgets/BarChart.swift +++ b/ModuleKit/Widgets/BarChart.swift @@ -12,7 +12,7 @@ import Cocoa import StatsKit -public class BarChart: Widget { +public class BarChart: WidgetWrapper { private var labelState: Bool = true private var boxState: Bool = true private var frameState: Bool = false @@ -26,7 +26,7 @@ public class BarChart: Widget { private var boxSettingsView: NSView? = nil private var frameSettingsView: NSView? = nil - public init(preview: Bool, title: String, config: NSDictionary?, store: UnsafePointer?) { + public init(title: String, config: NSDictionary?, store: UnsafePointer?, preview: Bool = false) { var widgetTitle: String = title self.store = store @@ -68,7 +68,7 @@ public class BarChart: Widget { y: Constants.Widget.margin.y, width: Constants.Widget.width + (2*Constants.Widget.margin.x), height: Constants.Widget.height - (2*Constants.Widget.margin.y) - ), preview: preview) + )) self.canDrawConcurrently = true @@ -228,13 +228,17 @@ public class BarChart: Widget { // MARK: - Settings - public override func settings(superview: NSView) { + public override func settings(width: CGFloat) -> NSView { let rowHeight: CGFloat = 30 let settingsNumber: CGFloat = 4 let height: CGFloat = ((rowHeight + Constants.Settings.margin) * settingsNumber) + Constants.Settings.margin - superview.setFrameSize(NSSize(width: superview.frame.width, height: height)) - let view: NSView = NSView(frame: NSRect(x: Constants.Settings.margin, y: Constants.Settings.margin, width: superview.frame.width - (Constants.Settings.margin*2), height: superview.frame.height - (Constants.Settings.margin*2))) + let view: NSView = NSView(frame: NSRect( + x: Constants.Settings.margin, + y: Constants.Settings.margin, + width: width - (Constants.Settings.margin*2), + height: height + )) view.addSubview(ToggleTitleRow( frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 3, width: view.frame.width, height: rowHeight), @@ -267,7 +271,7 @@ public class BarChart: Widget { selected: self.colorState.rawValue )) - superview.addSubview(view) + return view } @objc private func toggleLabel(_ sender: NSControl) { diff --git a/ModuleKit/Widgets/Battery.swift b/ModuleKit/Widgets/Battery.swift index 098596a1..b990c241 100644 --- a/ModuleKit/Widgets/Battery.swift +++ b/ModuleKit/Widgets/Battery.swift @@ -12,7 +12,7 @@ import Cocoa import StatsKit -public class BatterykWidget: Widget { +public class BatterykWidget: WidgetWrapper { private var additional: String = "none" private var timeFormat: String = "short" private var iconState: Bool = true @@ -26,7 +26,7 @@ public class BatterykWidget: Widget { private var charging: Bool = false private var ACStatus: Bool = false - public init(preview: Bool, title: String, config: NSDictionary?, store: UnsafePointer?) { + public init(title: String, config: NSDictionary?, store: UnsafePointer?, preview: Bool = false) { let widgetTitle: String = title self.store = store @@ -35,11 +35,11 @@ public class BatterykWidget: Widget { y: Constants.Widget.margin.y, width: 30 + (2*Constants.Widget.margin.x), height: Constants.Widget.height - (2*Constants.Widget.margin.y) - ), preview: preview) + )) self.canDrawConcurrently = true - if self.store != nil { + if self.store != nil && !preview { self.additional = store!.pointee.string(key: "\(self.title)_\(self.type.rawValue)_additional", defaultValue: self.additional) self.timeFormat = store!.pointee.string(key: "\(self.title)_timeFormat", defaultValue: self.timeFormat) self.iconState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_icon", defaultValue: self.iconState) @@ -265,16 +265,15 @@ public class BatterykWidget: Widget { // MARK: - Settings - public override func settings(superview: NSView) { + public override func settings(width: CGFloat) -> NSView { let rowHeight: CGFloat = 30 let height: CGFloat = ((rowHeight + Constants.Settings.margin) * 3) + Constants.Settings.margin - superview.setFrameSize(NSSize(width: superview.frame.width, height: height)) let view: NSView = NSView(frame: NSRect( x: Constants.Settings.margin, y: Constants.Settings.margin, - width: superview.frame.width - (Constants.Settings.margin*2), - height: superview.frame.height - (Constants.Settings.margin*2) + width: width - (Constants.Settings.margin*2), + height: height )) view.addSubview(SelectRow( @@ -299,7 +298,7 @@ public class BatterykWidget: Widget { state: self.colorState )) - superview.addSubview(view) + return view } @objc private func toggleAdditional(_ sender: NSMenuItem) { diff --git a/ModuleKit/Widgets/LineChart.swift b/ModuleKit/Widgets/LineChart.swift index ffb8059c..e9e6a0b0 100644 --- a/ModuleKit/Widgets/LineChart.swift +++ b/ModuleKit/Widgets/LineChart.swift @@ -12,7 +12,7 @@ import Cocoa import StatsKit -public class LineChart: Widget { +public class LineChart: WidgetWrapper { private var labelState: Bool = true private var boxState: Bool = true private var frameState: Bool = false @@ -36,7 +36,7 @@ public class LineChart: Widget { private var boxSettingsView: NSView? = nil private var frameSettingsView: NSView? = nil - public init(preview: Bool, title: String, config: NSDictionary?, store: UnsafePointer?) { + public init(title: String, config: NSDictionary?, store: UnsafePointer?, preview: Bool = false) { var widgetTitle: String = title self.store = store if config != nil { @@ -69,7 +69,7 @@ public class LineChart: Widget { y: Constants.Widget.margin.y, width: self.width + (2*Constants.Widget.margin.x), height: Constants.Widget.height - (2*Constants.Widget.margin.y) - ), preview: preview) + )) self.canDrawConcurrently = true @@ -247,13 +247,17 @@ public class LineChart: Widget { // MARK: - Settings - public override func settings(superview: NSView) { + public override func settings(width: CGFloat) -> NSView { let rowHeight: CGFloat = 30 let settingsNumber: CGFloat = 6 let height: CGFloat = ((rowHeight + Constants.Settings.margin) * settingsNumber) + Constants.Settings.margin - superview.setFrameSize(NSSize(width: superview.frame.width, height: height)) - let view: NSView = NSView(frame: NSRect(x: Constants.Settings.margin, y: Constants.Settings.margin, width: superview.frame.width - (Constants.Settings.margin*2), height: superview.frame.height - (Constants.Settings.margin*2))) + let view: NSView = NSView(frame: NSRect( + x: Constants.Settings.margin, + y: Constants.Settings.margin, + width: width - (Constants.Settings.margin*2), + height: height + )) view.addSubview(ToggleTitleRow( frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 5, width: view.frame.width, height: rowHeight), @@ -300,7 +304,7 @@ public class LineChart: Widget { state: self.valueColorState )) - superview.addSubview(view) + return view } @objc private func toggleLabel(_ sender: NSControl) { diff --git a/ModuleKit/Widgets/Memory.swift b/ModuleKit/Widgets/Memory.swift index 315d276c..e9df52ab 100644 --- a/ModuleKit/Widgets/Memory.swift +++ b/ModuleKit/Widgets/Memory.swift @@ -12,13 +12,13 @@ import Cocoa import StatsKit -public class MemoryWidget: Widget { +public class MemoryWidget: WidgetWrapper { private var orderReversedState: Bool = false private var value: (String, String) = ("0", "0") private let store: UnsafePointer? - public init(preview: Bool, title: String, config: NSDictionary?, store: UnsafePointer?) { + public init(title: String, config: NSDictionary?, store: UnsafePointer?, preview: Bool = false) { self.store = store if config != nil { var configuration = config! @@ -42,11 +42,11 @@ public class MemoryWidget: Widget { y: Constants.Widget.margin.y, width: 62, height: Constants.Widget.height - (2*Constants.Widget.margin.y) - ), preview: preview) + )) self.canDrawConcurrently = true - if self.store != nil { + if self.store != nil && !preview { self.orderReversedState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_orderReversed", defaultValue: self.orderReversedState) } @@ -102,12 +102,16 @@ public class MemoryWidget: Widget { }) } - public override func settings(superview: NSView) { + public override func settings(width: CGFloat) -> NSView { let rowHeight: CGFloat = 30 let height: CGFloat = ((rowHeight + Constants.Settings.margin) * 1) + Constants.Settings.margin - superview.setFrameSize(NSSize(width: superview.frame.width, height: height)) - let view: NSView = NSView(frame: NSRect(x: Constants.Settings.margin, y: Constants.Settings.margin, width: superview.frame.width - (Constants.Settings.margin*2), height: superview.frame.height - (Constants.Settings.margin*2))) + let view: NSView = NSView(frame: NSRect( + x: Constants.Settings.margin, + y: Constants.Settings.margin, + width: width - (Constants.Settings.margin*2), + height: height + )) view.addSubview(ToggleTitleRow( frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 0, width: view.frame.width, height: rowHeight), @@ -116,7 +120,7 @@ public class MemoryWidget: Widget { state: self.orderReversedState )) - superview.addSubview(view) + return view } @objc private func toggleOrder(_ sender: NSControl) { diff --git a/ModuleKit/Widgets/Mini.swift b/ModuleKit/Widgets/Mini.swift index 1915c039..b14ff689 100644 --- a/ModuleKit/Widgets/Mini.swift +++ b/ModuleKit/Widgets/Mini.swift @@ -12,7 +12,7 @@ import Cocoa import StatsKit -public class Mini: Widget { +public class Mini: WidgetWrapper { private let store: UnsafePointer? private let defaultTitle: String @@ -34,7 +34,7 @@ public class Mini: Widget { } } - public init(preview: Bool, title: String, config: NSDictionary?, store: UnsafePointer?) { + public init(title: String, config: NSDictionary?, store: UnsafePointer?, preview: Bool = false) { self.store = store var widgetTitle: String = title if config != nil { @@ -73,11 +73,11 @@ public class Mini: Widget { y: Constants.Widget.margin.y, width: Constants.Widget.width + (2*Constants.Widget.margin.x), height: Constants.Widget.height - (2*Constants.Widget.margin.y) - ), preview: preview) + )) self.wantsLayer = true - if let store = self.store { + if let store = self.store, !preview { self.colorState = widget_c(rawValue: store.pointee.string(key: "\(self.title)_\(self.type.rawValue)_color", defaultValue: self.colorState.rawValue)) ?? self.colorState self.labelState = store.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_label", defaultValue: self.labelState) } @@ -169,12 +169,16 @@ public class Mini: Widget { // MARK: - Settings - public override func settings(superview: NSView) { + public override func settings(width: CGFloat) -> NSView { let height: CGFloat = 60 + (Constants.Settings.margin*3) let rowHeight: CGFloat = 30 - superview.setFrameSize(NSSize(width: superview.frame.width, height: height)) - let view: NSView = NSView(frame: NSRect(x: Constants.Settings.margin, y: Constants.Settings.margin, width: superview.frame.width - (Constants.Settings.margin*2), height: superview.frame.height - (Constants.Settings.margin*2))) + let view: NSView = NSView(frame: NSRect( + x: Constants.Settings.margin, + y: Constants.Settings.margin, + width: width - (Constants.Settings.margin*2), + height: height + )) view.addSubview(ToggleTitleRow( frame: NSRect(x: 0, y: rowHeight + Constants.Settings.margin, width: view.frame.width, height: rowHeight), @@ -191,7 +195,7 @@ public class Mini: Widget { selected: self.colorState.rawValue )) - superview.addSubview(view) + return view } @objc private func toggleColor(_ sender: NSMenuItem) { diff --git a/ModuleKit/Widgets/NetworkChart.swift b/ModuleKit/Widgets/NetworkChart.swift index 072b76e9..27b5efcb 100644 --- a/ModuleKit/Widgets/NetworkChart.swift +++ b/ModuleKit/Widgets/NetworkChart.swift @@ -12,7 +12,7 @@ import Cocoa import StatsKit -public class NetworkChart: Widget { +public class NetworkChart: WidgetWrapper { private var boxState: Bool = false private var frameState: Bool = false @@ -31,7 +31,7 @@ public class NetworkChart: Widget { private var boxSettingsView: NSView? = nil private var frameSettingsView: NSView? = nil - public init(preview: Bool, title: String, config: NSDictionary?, store: UnsafePointer?) { + public init(title: String, config: NSDictionary?, store: UnsafePointer?, preview: Bool = false) { var widgetTitle: String = title self.store = store if config != nil { @@ -45,7 +45,7 @@ public class NetworkChart: Widget { y: Constants.Widget.margin.y, width: self.width + (2*Constants.Widget.margin.x), height: Constants.Widget.height - (2*Constants.Widget.margin.y) - ), preview: preview) + )) self.wantsLayer = true self.canDrawConcurrently = true @@ -119,13 +119,17 @@ public class NetworkChart: Widget { // MARK: - Settings - public override func settings(superview: NSView) { + public override func settings(width: CGFloat) -> NSView { let rowHeight: CGFloat = 30 let settingsNumber: CGFloat = 2 let height: CGFloat = ((rowHeight + Constants.Settings.margin) * settingsNumber) + Constants.Settings.margin - superview.setFrameSize(NSSize(width: superview.frame.width, height: height)) - let view: NSView = NSView(frame: NSRect(x: Constants.Settings.margin, y: Constants.Settings.margin, width: superview.frame.width - (Constants.Settings.margin*2), height: superview.frame.height - (Constants.Settings.margin*2))) + let view: NSView = NSView(frame: NSRect( + x: Constants.Settings.margin, + y: Constants.Settings.margin, + width: width - (Constants.Settings.margin*2), + height: height + )) self.boxSettingsView = ToggleTitleRow( frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 1, width: view.frame.width, height: rowHeight), @@ -143,7 +147,7 @@ public class NetworkChart: Widget { ) view.addSubview(self.frameSettingsView!) - superview.addSubview(view) + return view } @objc private func toggleBox(_ sender: NSControl) { diff --git a/ModuleKit/Widgets/PieChart.swift b/ModuleKit/Widgets/PieChart.swift index 73413bec..9c24d458 100644 --- a/ModuleKit/Widgets/PieChart.swift +++ b/ModuleKit/Widgets/PieChart.swift @@ -12,7 +12,7 @@ import Cocoa import StatsKit -public class PieChart: Widget { +public class PieChart: WidgetWrapper { private var labelState: Bool = true private let store: UnsafePointer? @@ -29,7 +29,7 @@ public class PieChart: Widget { private let size: CGFloat = Constants.Widget.height - (Constants.Widget.margin.y*2) + (Constants.Widget.margin.x*2) - public init(preview: Bool, title: String, config: NSDictionary?, store: UnsafePointer?) { + public init(title: String, config: NSDictionary?, store: UnsafePointer?, preview: Bool = false) { var widgetTitle: String = title self.store = store if config != nil { @@ -43,12 +43,12 @@ public class PieChart: Widget { y: Constants.Widget.margin.y, width: self.size, height: Constants.Widget.height - (Constants.Widget.margin.y*2) - ), preview: preview) + )) self.wantsLayer = true self.canDrawConcurrently = true - if let store = self.store { + if let store = self.store, !preview { self.labelState = store.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_label", defaultValue: self.labelState) } @@ -87,7 +87,7 @@ public class PieChart: Widget { frame = NSRect(x: x, y: 0, width: self.frame.size.height, height: self.frame.size.height) self.chart.frame = frame - self.setFrameSize(NSSize(width: self.size + x, height: self.frame.size.height)) + self.setWidth(self.size + x) } public func setValue(_ segments: [circle_segment]) { @@ -98,16 +98,15 @@ public class PieChart: Widget { // MARK: - Settings - public override func settings(superview: NSView) { + public override func settings(width: CGFloat) -> NSView { let rowHeight: CGFloat = 30 let height: CGFloat = ((rowHeight + Constants.Settings.margin) * 1) + Constants.Settings.margin - superview.setFrameSize(NSSize(width: superview.frame.width, height: height)) let view: NSView = NSView(frame: NSRect( x: Constants.Settings.margin, y: Constants.Settings.margin, - width: superview.frame.width - (Constants.Settings.margin*2), - height: superview.frame.height - (Constants.Settings.margin*2) + width: width - (Constants.Settings.margin*2), + height: height )) view.addSubview(ToggleTitleRow( @@ -117,7 +116,7 @@ public class PieChart: Widget { state: self.labelState )) - superview.addSubview(view) + return view } @objc private func toggleLabel(_ sender: NSControl) { diff --git a/ModuleKit/Widgets/Sensors.swift b/ModuleKit/Widgets/Sensors.swift index 1c81c1db..67324e91 100644 --- a/ModuleKit/Widgets/Sensors.swift +++ b/ModuleKit/Widgets/Sensors.swift @@ -12,14 +12,14 @@ import Cocoa import StatsKit -public class SensorsWidget: Widget { +public class SensorsWidget: WidgetWrapper { private var modeState: String = "automatic" private let store: UnsafePointer? private var body: CALayer = CALayer() private var values: [KeyValue_t] = [] - public init(preview: Bool, title: String, config: NSDictionary?, store: UnsafePointer?) { + public init(title: String, config: NSDictionary?, store: UnsafePointer?, preview: Bool = false) { self.store = store if config != nil { var configuration = config! @@ -41,7 +41,7 @@ public class SensorsWidget: Widget { y: Constants.Widget.margin.y, width: Constants.Widget.width, height: Constants.Widget.height - (2*Constants.Widget.margin.y) - ), preview: preview) + )) self.modeState = store?.pointee.string(key: "\(self.title)_\(self.type.rawValue)_mode", defaultValue: self.modeState) ?? self.modeState @@ -212,16 +212,15 @@ public class SensorsWidget: Widget { // MARK: - Settings - public override func settings(superview: NSView) { + public override func settings(width: CGFloat) -> NSView { let rowHeight: CGFloat = 30 let height: CGFloat = ((rowHeight + Constants.Settings.margin) * 1) + Constants.Settings.margin - superview.setFrameSize(NSSize(width: superview.frame.width, height: height)) let view: NSView = NSView(frame: NSRect( x: Constants.Settings.margin, y: Constants.Settings.margin, - width: superview.frame.width - (Constants.Settings.margin*2), - height: superview.frame.height - (Constants.Settings.margin*2) + width: width - (Constants.Settings.margin*2), + height: height )) view.addSubview(SelectRow( @@ -232,7 +231,7 @@ public class SensorsWidget: Widget { selected: self.modeState )) - superview.addSubview(view) + return view } @objc private func changeMode(_ sender: NSMenuItem) { diff --git a/ModuleKit/Widgets/Speed.swift b/ModuleKit/Widgets/Speed.swift index 120ac2a6..27e1b38c 100644 --- a/ModuleKit/Widgets/Speed.swift +++ b/ModuleKit/Widgets/Speed.swift @@ -12,7 +12,7 @@ import Cocoa import StatsKit -public class SpeedWidget: Widget { +public class SpeedWidget: WidgetWrapper { private var icon: String = "dots" private var state: Bool = false private var valueState: Bool = true @@ -29,7 +29,7 @@ public class SpeedWidget: Widget { private let store: UnsafePointer? private var width: CGFloat = 58 - public init(preview: Bool, title: String, config: NSDictionary?, store: UnsafePointer?) { + public init(title: String, config: NSDictionary?, store: UnsafePointer?, preview: Bool = false) { let widgetTitle: String = title self.store = store if config != nil { @@ -46,11 +46,11 @@ public class SpeedWidget: Widget { y: Constants.Widget.margin.y, width: width, height: Constants.Widget.height - (2*Constants.Widget.margin.y) - ), preview: preview) + )) self.canDrawConcurrently = true - if self.store != nil { + if self.store != nil && !preview { self.valueState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_value", defaultValue: self.valueState) self.icon = store!.pointee.string(key: "\(self.title)_\(self.type.rawValue)_icon", defaultValue: self.baseValue) self.baseValue = store!.pointee.string(key: "\(self.title)_base", defaultValue: self.baseValue) @@ -211,16 +211,15 @@ public class SpeedWidget: Widget { } } - public override func settings(superview: NSView) { + public override func settings(width: CGFloat) -> NSView { let height: CGFloat = 90 + (Constants.Settings.margin*4) let rowHeight: CGFloat = 30 - superview.setFrameSize(NSSize(width: superview.frame.width, height: height)) let view: NSView = NSView(frame: NSRect( x: Constants.Settings.margin, y: Constants.Settings.margin, - width: superview.frame.width - (Constants.Settings.margin*2), - height: superview.frame.height - (Constants.Settings.margin*2) + width: width - (Constants.Settings.margin*2), + height: height )) view.addSubview(SelectRow( @@ -246,7 +245,7 @@ public class SpeedWidget: Widget { state: self.valueState )) - superview.addSubview(view) + return view } @objc private func toggleValue(_ sender: NSControl) { diff --git a/ModuleKit/module.swift b/ModuleKit/module.swift index cfe2f08a..8c417630 100644 --- a/ModuleKit/module.swift +++ b/ModuleKit/module.swift @@ -14,7 +14,6 @@ public protocol Module_p { var available: Bool { get } var enabled: Bool { get } - var widget: Widget_p? { get } var settings: Settings_p? { get } func mount() @@ -73,7 +72,7 @@ open class Module: Module_p { public var available: Bool = false public var enabled: Bool = false - public var widget: Widget_p? = nil + public var widgets: [Widget] = [] public var settings: Settings_p? = nil private var settingsView: Settings_v? = nil @@ -83,16 +82,6 @@ open class Module: Module_p { private let log: OSLog private var store: UnsafePointer private var readers: [Reader_p] = [] - private var menuBarItem: NSStatusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength) - private var activeWidget: widget_t { - get { - let widgetStr = self.store.pointee.string(key: "\(self.config.name)_widget", defaultValue: self.config.defaultWidget.rawValue) - return widget_t.allCases.first{ $0.rawValue == widgetStr } ?? widget_t.unknown - } - set {} - } - private var ready: Bool = false - private var widgetLoaded: Bool = false public init(store: UnsafePointer, popup: Popup_p?, settings: Settings_v?) { self.config = module_c(in: Bundle(for: type(of: self)).path(forResource: "config", ofType: "plist")!) @@ -103,14 +92,10 @@ open class Module: Module_p { self.popupView = popup self.available = self.isAvailable() self.enabled = self.store.pointee.bool(key: "\(self.config.name)_state", defaultValue: self.config.defaultState) - self.menuBarItem.autosaveName = self.config.name - self.menuBarItem.isVisible = self.enabled if !self.available { os_log(.debug, log: log, "Module is not available") - self.menuBarItem.length = 0 - self.menuBarItem.isVisible = false if self.enabled { self.enabled = false self.store.pointee.set(key: "\(self.config.name)_state", value: false) @@ -119,26 +104,23 @@ open class Module: Module_p { return } - NotificationCenter.default.addObserver(self, selector: #selector(listenForWidgetSwitch), name: .switchWidget, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(listenForMouseDownInSettings), name: .clickInSettings, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(listenForModuleToggle), name: .toggleModule, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(listenForPopupToggle), name: .togglePopup, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(listenForToggleWidget), name: .toggleWidget, object: nil) if self.config.widgetsConfig.count != 0 { - self.initWidget() + self.initWidgets() } else { os_log(.debug, log: log, "Module started without widget") } - self.settings = Settings(config: &self.config, enabled: self.enabled, activeWidget: self.widget, moduleSettings: self.settingsView) + self.settings = Settings(store: store, config: &self.config, widgets: &self.widgets, enabled: self.enabled, moduleSettings: self.settingsView) self.settings?.toggleCallback = { [weak self] in self?.toggleEnabled() } self.popup = PopupWindow(title: self.config.name, view: self.popupView, visibilityCallback: self.visibilityCallback) - - self.menuBarItem.button?.target = self - self.menuBarItem.button?.action = #selector(self.togglePopup) - self.menuBarItem.button?.sendAction(on: [.leftMouseDown, .rightMouseDown]) } deinit { @@ -155,6 +137,7 @@ open class Module: Module_p { reader.initStoreValues(title: self.config.name, store: self.store) reader.start() } + self.widgets.forEach{ $0.enable() } } // disable module @@ -170,7 +153,7 @@ open class Module: Module_p { $0.stop() $0.terminate() } - NSStatusBar.system.removeStatusItem(self.menuBarItem) + self.widgets.forEach{ $0.disable() } os_log(.debug, log: log, "Module terminated") } @@ -187,12 +170,7 @@ open class Module: Module_p { reader.initStoreValues(title: self.config.name, store: self.store) reader.start() } - self.menuBarItem.isVisible = true - if self.widget != nil { - self.loadWidget() - } else { - self.initWidget() - } + self.widgets.forEach{ $0.enable() } os_log(.debug, log: log, "Module enabled") } @@ -203,7 +181,7 @@ open class Module: Module_p { self.enabled = false self.store.pointee.set(key: "\(self.config.name)_state", value: false) self.readers.forEach{ $0.stop() } - self.menuBarItem.isVisible = false + self.widgets.forEach{ $0.disable() } self.popup?.setIsVisible(false) os_log(.debug, log: log, "Module disabled") } @@ -223,22 +201,6 @@ open class Module: Module_p { os_log(.debug, log: log, "Reader %s was added", "\(reader.self)") } - // handler for reader, calls when main reader is ready, and return first value - public func readyHandler() { - os_log(.debug, log: log, "Reader report readiness") - self.ready = true - - if !self.widgetLoaded { - self.loadWidget() - } - } - - // change menu item width - public func widgetWidthHandler(_ width: CGFloat) { - os_log(.debug, log: log, "Widget %s change width to %.2f", "\(type(of: self.widget!))", width) - self.menuBarItem.length = width - } - // replace a popup view public func replacePopup(_ view: Popup_p) { self.popup?.setIsVisible(false) @@ -249,51 +211,15 @@ open class Module: Module_p { // determine if module is available (can be overrided in module) open func isAvailable() -> Bool { return true } - // setup menu ber item - private func loadWidget() { - guard self.available && self.enabled && self.ready && self.widget != nil else { return } - - DispatchQueue.main.async { - self.menuBarItem.length = self.widget!.frame.width - self.menuBarItem.button?.subviews.forEach{ $0.removeFromSuperview() } - self.menuBarItem.button?.addSubview(self.widget!) - self.widgetLoaded = true - self.widgetDidSet(self.widget?.type ?? .unknown) - } - } - - // load the widget and set up. Calls when module init or widget change - private func initWidget() { + // load the widget and set up. Calls when module init + private func initWidgets() { guard self.available else { return } - self.widget = self.activeWidget.new(module: self.config.name, config: self.config.widgetsConfig, store: self.store) - if self.widget == nil { - self.enabled = false - os_log(.error, log: log, "widget with type %s not found", "\(self.activeWidget)") - return + self.config.availableWidgets.forEach { (widgetType: widget_t) in + if let widget = widgetType.new(module: self.config.name, config: self.config.widgetsConfig, store: self.store) { + self.widgets.append(widget) + } } - os_log(.debug, log: log, "Successfully initialize widget: %s", "\(String(describing: self.widget!))") - - self.widget?.widthHandler = { [weak self] value in - self?.widgetWidthHandler(value) - } - - DispatchQueue.global(qos: .background).async { - self.readers.forEach{ $0.read() } - } - - if let mainReader = self.readers.first(where: { !$0.optional }) { - self.widget?.setValues(mainReader.getHistory()) - } - - if self.ready && self.enabled { - self.menuBarItem.length = self.widget!.frame.width - self.menuBarItem.button?.subviews.forEach{ $0.removeFromSuperview() } - self.menuBarItem.button?.addSubview(self.widget!) - self.widgetLoaded = true - } - - self.settings?.setActiveWidget(self.widget) } // call after widget set up @@ -312,25 +238,26 @@ open class Module: Module_p { } } - @objc private func togglePopup(_ sender: Any) { - let openedWindows = NSApplication.shared.windows.filter{ $0 is NSPanel } - openedWindows.forEach{ $0.setIsVisible(false) } - - guard let popup = self.popup else { + @objc private func listenForPopupToggle(_ notification: Notification) { + guard let popup = self.popup, + let name = notification.userInfo?["module"] as? String, + let buttonOrigin = notification.userInfo?["origin"] as? CGPoint, + let buttonCenter = notification.userInfo?["center"] as? CGFloat, + self.config.name == name else { return } + let openedWindows = NSApplication.shared.windows.filter{ $0 is NSPanel } + openedWindows.forEach{ $0.setIsVisible(false) } + if popup.occlusionState.rawValue == 8192 { NSApplication.shared.activate(ignoringOtherApps: true) popup.contentView?.invalidateIntrinsicContentSize() - let buttonOrigin = self.menuBarItem.button?.window?.frame.origin - let buttonCenter = (self.menuBarItem.button?.window?.frame.width)! / 2 - let windowCenter = popup.contentView!.intrinsicContentSize.width / 2 - var x = buttonOrigin!.x - windowCenter + buttonCenter - let y = buttonOrigin!.y - popup.contentView!.intrinsicContentSize.height - 3 + var x = buttonOrigin.x - windowCenter + buttonCenter + let y = buttonOrigin.y - popup.contentView!.intrinsicContentSize.height - 3 let maxWidth = NSScreen.screens.map{ $0.frame.width }.reduce(0, +) if x + popup.contentView!.intrinsicContentSize.width > maxWidth { @@ -359,25 +286,25 @@ open class Module: Module_p { } } - @objc private func listenForWidgetSwitch(_ notification: Notification) { - if let moduleName = notification.userInfo?["module"] as? String { - if let widgetName = notification.userInfo?["widget"] as? String { - if moduleName == self.config.name { - if let widgetType = widget_t.allCases.first(where: { $0.rawValue == widgetName }) { - self.activeWidget = widgetType - self.store.pointee.set(key: "\(self.config.name)_widget", value: widgetType.rawValue) - self.initWidget() - self.widgetDidSet(widgetType) - os_log(.debug, log: log, "Widget is changed to: %s", "\(widgetName)") - } - } - } - } - } - @objc private func listenForMouseDownInSettings(_ notification: Notification) { if let popup = self.popup, popup.isVisible { self.popup?.setIsVisible(false) } } + + @objc private func listenForToggleWidget(_ notification: Notification) { + guard let name = notification.userInfo?["module"] as? String, name == self.config.name else { + return + } + let count = self.widgets.filter({ $0.isActive }).count + var state = self.enabled + + if count == 0 && self.enabled { + state = false + } else if count != 0 && !self.enabled { + state = true + } + + NotificationCenter.default.post(name: .toggleModule, object: nil, userInfo: ["module": self.config.name, "state": state]) + } } diff --git a/ModuleKit/settings.swift b/ModuleKit/settings.swift index 881ad020..3f85fb3e 100644 --- a/ModuleKit/settings.swift +++ b/ModuleKit/settings.swift @@ -14,53 +14,50 @@ import StatsKit public protocol Settings_p: NSView { var toggleCallback: () -> () { get set } - func setActiveWidget(_ widget: Widget_p?) } public protocol Settings_v: NSView { var callback: (() -> Void) { get set } - func load(widget: widget_t) + func load(widgets: [widget_t]) } open class Settings: NSView, Settings_p { public var toggleCallback: () -> () = {} private let headerHeight: CGFloat = 42 - private var widgetSelectorHeight: CGFloat = Constants.Widget.height + (Constants.Settings.margin*2) - - private var settingsView: NSView = NSView() - - private var widgetSelectorView: NSView? = nil - private var widgetSettingsView: NSView? = nil - private var moduleSettingsView: NSView? = nil private var config: UnsafePointer - private var activeWidget: Widget_p? + private var store: UnsafePointer + private var widgets: UnsafeMutablePointer<[Widget]> + + private var activeWidget: Widget? { + get { + return self.widgets.pointee.first{ $0.isActive } + } + } private var moduleSettings: Settings_v? private var enableControl: NSControl? + private var container: ScrollableStackView? + private var widgetSettings: widget_t? + private var moduleSettingsContainer: NSView? - init(config: UnsafePointer, enabled: Bool, activeWidget: Widget_p?, moduleSettings: Settings_v?) { + init(store: UnsafePointer, config: UnsafePointer, widgets: UnsafeMutablePointer<[Widget]>, enabled: Bool, moduleSettings: Settings_v?) { + self.store = store self.config = config - self.activeWidget = activeWidget + self.widgets = widgets self.moduleSettings = moduleSettings + super.init(frame: NSRect(x: 0, y: 0, width: Constants.Settings.width, height: Constants.Settings.height)) + self.wantsLayer = true self.appearance = NSAppearance(named: .aqua) self.layer?.backgroundColor = NSColor(hexString: "#ececec").cgColor NotificationCenter.default.addObserver(self, selector: #selector(externalModuleToggle), name: .toggleModule, object: nil) - self.addHeader(state: enabled) - self.addSettings() - - self.addWidgetSelector() - self.addWidgetSettings() - - if self.moduleSettings != nil { - self.moduleSettings?.load(widget: self.activeWidget?.type ?? .unknown) - self.addModuleSettings() - } + self.addSubview(self.header(state: enabled)) + self.addSubview(self.body()) } deinit { @@ -71,157 +68,9 @@ open class Settings: NSView, Settings_p { fatalError("init(coder:) has not been implemented") } - private func addSettings() { - let view: NSScrollView = NSScrollView(frame: NSRect( - x: 0, - y: 0, - width: self.frame.width, - height: Constants.Settings.height - self.headerHeight - )) - view.wantsLayer = true - view.backgroundColor = NSColor(hexString: "#ececec") - - view.translatesAutoresizingMaskIntoConstraints = true - view.borderType = .noBorder - view.hasVerticalScroller = true - view.hasHorizontalScroller = false - view.autohidesScrollers = true - view.horizontalScrollElasticity = .none - - let settings: NSView = FlippedView(frame: NSRect(x: 0, y: 0, width: view.frame.width, height: 0)) - settings.wantsLayer = true - settings.layer?.backgroundColor = NSColor(hexString: "#ececec").cgColor - - view.documentView = settings - - self.addSubview(view) - self.settingsView = settings - } + // MARK: - Views - private func addWidgetSelector() { - if self.config.pointee.availableWidgets.count == 0 { - self.widgetSelectorHeight = 0 - return - } - - let view: NSView = NSView(frame: NSRect( - x : Constants.Settings.margin, - y: Constants.Settings.margin, - width: self.settingsView.frame.width - (Constants.Settings.margin*2), - height: self.widgetSelectorHeight - )) - view.wantsLayer = true - view.layer?.backgroundColor = .white - view.layer!.cornerRadius = 3 - - var x: CGFloat = Constants.Settings.margin - for i in 0...self.config.pointee.availableWidgets.count - 1 { - let widgetType = self.config.pointee.availableWidgets[i] - if let widget = widgetType.new(module: self.config.pointee.name, config: self.config.pointee.widgetsConfig, store: nil, preview: true) { - let preview = WidgetPreview( - frame: NSRect( - x: x, - y: Constants.Settings.margin, - width: widget.frame.width + (Constants.Widget.spacing*2), - height: self.widgetSelectorHeight - (Constants.Settings.margin*2) - ), - title: self.config.pointee.name, - widget: widget, - state: self.activeWidget?.type == widgetType - ) - preview.widthCallback = { [weak self] in - self?.recalculateWidgetSelectorOptionsWidth() - } - view.addSubview(preview) - x += preview.frame.width + Constants.Settings.margin - } - } - - self.settingsView.addSubview(view) - self.widgetSelectorView = view - self.resize() - } - - private func addWidgetSettings() { - if self.activeWidget == nil { - return - } - - var y: CGFloat = Constants.Settings.margin - if self.widgetSelectorView != nil { - y += self.widgetSelectorView!.frame.height + Constants.Settings.margin - } - - let view: NSView = NSView(frame: NSRect( - x: Constants.Settings.margin, - y: y, - width: self.settingsView.frame.width - (Constants.Settings.margin*2), - height: 0 - )) - view.wantsLayer = true - view.layer?.backgroundColor = .white - view.layer!.cornerRadius = 3 - - self.activeWidget?.settings(superview: view) - - if view.frame.height != 0 { - self.settingsView.addSubview(view) - self.widgetSettingsView = view - self.resize() - } - } - - private func addModuleSettings() { - if self.moduleSettings == nil || self.moduleSettings?.frame.height == 0 { - return - } - - var y: CGFloat = Constants.Settings.margin - if self.widgetSelectorView != nil { - y += self.widgetSelectorView!.frame.height + Constants.Settings.margin - } - if self.widgetSettingsView != nil { - y += self.widgetSettingsView!.frame.height + Constants.Settings.margin - } - - let view: NSView = NSView(frame: NSRect( - x: Constants.Settings.margin, - y: y, - width: self.settingsView.frame.width - (Constants.Settings.margin*2), - height: self.moduleSettings?.frame.height ?? 0 - )) - view.wantsLayer = true - view.layer?.backgroundColor = .white - view.layer!.cornerRadius = 3 - - view.addSubview(self.moduleSettings!) - - self.settingsView.addSubview(view) - self.moduleSettingsView = view - self.resize() - } - - private func resize() { - var height: CGFloat = Constants.Settings.margin - - self.settingsView.subviews.forEach({ (v: NSView) in - height += v.frame.height + Constants.Settings.margin - }) - - if self.settingsView.frame.height != height { - self.settingsView.setFrameSize(NSSize(width: self.settingsView.frame.width, height: height)) - } - } - - private func recalculateWidgetSelectorOptionsWidth() { - var x: CGFloat = Constants.Settings.margin - self.widgetSelectorView?.subviews.forEach({ (v: NSView) in - v.setFrameOrigin(NSPoint(x: x, y: v.frame.origin.y)) - x += v.frame.width + Constants.Settings.margin - }) - } - - private func addHeader(state: Bool) { + private func header(state: Bool) -> NSView { let view: NSView = NSView(frame: NSRect(x: 0, y: self.frame.height - self.headerHeight, width: self.frame.width, height: self.headerHeight)) view.wantsLayer = true @@ -265,16 +114,134 @@ open class Settings: NSView, Settings_p { view.addSubview(titleView) view.addSubview(toggle) view.addSubview(line) - self.enableControl = toggle - self.addSubview(view) + + return view } - @objc func toggleEnable(_ sender: Any) { + private func body() -> NSView { + let view = ScrollableStackView(frame: NSRect(x: 0, y: 0, width: self.frame.width, height: Constants.Settings.height - self.headerHeight)) + view.stackView.edgeInsets = NSEdgeInsets( + top: Constants.Settings.margin, + left: Constants.Settings.margin, + bottom: Constants.Settings.margin, + right: Constants.Settings.margin + ) + view.stackView.spacing = Constants.Settings.margin + + view.wantsLayer = true + view.layer?.backgroundColor = NSColor(hexString: "#ececec").cgColor + self.container = view + + self.initWidgetSelector() + self.initModuleSettings() + + return view + } + + private func initWidgetSelector() { + let container: NSView = NSView(frame: NSRect(x: 0, y: 0, width: 0, height: Constants.Widget.height + (Constants.Settings.margin*2))) + container.wantsLayer = true + container.layer?.backgroundColor = .white + container.layer?.cornerRadius = 3 + + let view: NSStackView = NSStackView() + view.orientation = .horizontal + view.translatesAutoresizingMaskIntoConstraints = false + view.edgeInsets = NSEdgeInsets( + top: Constants.Settings.margin, + left: Constants.Settings.margin, + bottom: Constants.Settings.margin, + right: Constants.Settings.margin + ) + view.spacing = Constants.Settings.margin + + for i in 0...self.widgets.pointee.count - 1 { + let preview = WidgetPreview(&self.widgets.pointee[i]) + preview.settingsCallback = { [weak self] value in + self?.toggleSettings(value) + } + preview.stateCallback = { [weak self] in + self?.widgetStateCallback() + } + view.addArrangedSubview(preview) + } + + container.addSubview(view) + + if let view = self.container { + view.stackView.addArrangedSubview(container) + } + + NSLayoutConstraint.activate([ + container.heightAnchor.constraint(equalToConstant: container.frame.height), + view.heightAnchor.constraint(equalTo: container.heightAnchor), + ]) + } + + private func initModuleSettings() { + guard let settingsView = self.moduleSettings else { + return + } + + let container: NSView = NSView(frame: NSRect(x: 0, y: 0, width: 0, height: 0)) + container.wantsLayer = true + container.layer?.backgroundColor = .white + container.layer?.cornerRadius = 3 + self.moduleSettingsContainer = container + + self.moduleSettings?.load(widgets: self.widgets.pointee.filter{ $0.isActive }.map{ $0.type }) + + container.addSubview(settingsView) + if let view = self.container { + view.stackView.addArrangedSubview(container) + } + + NSLayoutConstraint.activate([ + container.heightAnchor.constraint(equalTo: settingsView.heightAnchor), + ]) + } + + // MARK: - helpers + + private func toggleSettings(_ type: widget_t) { + guard let widget = self.widgets.pointee.first(where: { $0.type == type }) else { + return + } + + let container: NSView = NSView() + container.wantsLayer = true + container.layer?.backgroundColor = .white + container.layer?.cornerRadius = 3 + + let width: CGFloat = (self.container?.clipView.bounds.width ?? self.frame.width) - (Constants.Settings.margin*2) + let settingsView = widget.item.settings(width: width) + container.addSubview(settingsView) + + if let view = self.container { + if self.widgetSettings == nil { + view.stackView.insertArrangedSubview(container, at: 1) + self.widgetSettings = type + } else if self.widgetSettings != nil && self.widgetSettings == type { + view.stackView.arrangedSubviews[1].removeFromSuperview() + self.widgetSettings = nil + } else { + view.stackView.arrangedSubviews[1].removeFromSuperview() + self.widgetSettings = type + view.stackView.insertArrangedSubview(container, at: 1) + } + } + + NSLayoutConstraint.activate([ + container.heightAnchor.constraint(equalTo: settingsView.heightAnchor), + ]) + } + + @objc private func toggleEnable(_ sender: Any) { self.toggleCallback() } - @objc func externalModuleToggle(_ notification: Notification) { + @objc private func externalModuleToggle(_ notification: Notification) { if let name = notification.userInfo?["module"] as? String { if name == self.config.pointee.name { if let state = notification.userInfo?["state"] as? Bool { @@ -284,113 +251,140 @@ open class Settings: NSView, Settings_p { } } - public func setActiveWidget(_ widget: Widget_p?) { - self.activeWidget = widget - - self.widgetSettingsView?.removeFromSuperview() - self.moduleSettingsView?.removeFromSuperview() - - self.widgetSettingsView = nil - self.addWidgetSettings() - - if self.moduleSettings != nil { - self.moduleSettings?.load(widget: self.activeWidget?.type ?? .unknown) - self.addModuleSettings() + @objc private func widgetStateCallback() { + guard let container = self.moduleSettingsContainer, let settingsView = self.moduleSettings else { + return } + + container.subviews.forEach{ $0.removeFromSuperview() } + settingsView.load(widgets: self.widgets.pointee.filter{ $0.isActive }.map{ $0.type }) + self.moduleSettingsContainer?.addSubview(settingsView) + + NSLayoutConstraint.activate([ + container.heightAnchor.constraint(equalTo: settingsView.heightAnchor), + ]) } } -open class FlippedView: NSView { - open override var isFlipped: Bool { true } -} - -class WidgetPreview: NSView { - private let type: widget_t - private var state: Bool - private let title: String +internal class WidgetPreview: NSStackView { + public var settingsCallback: (widget_t) -> Void = {_ in } + public var stateCallback: () -> Void = {} - public var widthCallback: () -> Void = {} + private var widget: UnsafeMutablePointer + private var size: CGFloat = Constants.Widget.height - public init(frame: NSRect, title: String, widget: Widget_p, state: Bool) { - self.type = widget.type - self.state = state - self.title = title + public init(_ widget: UnsafeMutablePointer) { + self.widget = widget - super.init(frame: frame) - - NotificationCenter.default.addObserver(self, selector: #selector(maybeActivate), name: .switchWidget, object: nil) + super.init(frame: NSRect( + x: 0, + y: 0, + width: widget.pointee.preview.frame.width + self.size + (Constants.Widget.spacing*2), + height: self.size + )) self.wantsLayer = true self.layer?.cornerRadius = 2 - self.layer?.borderColor = self.state ? NSColor.systemBlue.cgColor : NSColor(hexString: "#dddddd").cgColor + self.layer?.borderColor = self.widget.pointee.isActive ? NSColor.systemBlue.cgColor : NSColor(hexString: "#dddddd").cgColor self.layer?.borderWidth = 1 + self.toolTip = LocalizedString("Select widget", widget.pointee.type.name()) - self.toolTip = LocalizedString("Select widget", widget.type.name()) + self.orientation = .horizontal + self.distribution = .fillProportionally + self.spacing = 0 let container: NSView = NSView(frame: NSRect( x: Constants.Widget.spacing, y: 0, - width: frame.width - (Constants.Widget.spacing*2), - height: frame.height + width: widget.pointee.preview.frame.width, + height: self.frame.height )) container.wantsLayer = true - container.addSubview(widget) + container.addSubview(widget.pointee.preview) - self.addSubview(container) + self.addArrangedSubview(container) + self.addArrangedSubview(self.separator()) + self.addArrangedSubview(self.button()) - widget.widthHandler = { [weak self] value in - self?.removeTrackingArea((self?.trackingAreas.first)!) - let newWidth = value + (Constants.Widget.spacing*2) - - let rect = NSRect(x: 0, y: 0, width: newWidth, height: self!.frame.height) - let trackingArea = NSTrackingArea(rect: rect, options: [NSTrackingArea.Options.activeAlways, NSTrackingArea.Options.mouseEnteredAndExited, NSTrackingArea.Options.activeInActiveApp], owner: self, userInfo: ["menu": self!.type]) - self?.addTrackingArea(trackingArea) - - DispatchQueue.main.async(execute: { - container.setFrameSize(NSSize(width: value, height: container.frame.height)) - self?.setFrameSize(NSSize(width: newWidth, height: self?.frame.height ?? Constants.Widget.height)) - self?.widthCallback() + widget.pointee.preview.widthHandler = { [weak self] value in + self?.trackingAreas.forEach({ (area: NSTrackingArea) in + self?.removeTrackingArea(area) }) + + let rect = NSRect(x: Constants.Widget.spacing, y: 0, width: value, height: self!.frame.height) + let trackingArea = NSTrackingArea(rect: rect, options: [NSTrackingArea.Options.activeAlways, NSTrackingArea.Options.mouseEnteredAndExited, NSTrackingArea.Options.activeInActiveApp], owner: self, userInfo: nil) + self?.addTrackingArea(trackingArea) } - let rect = NSRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height) - let trackingArea = NSTrackingArea(rect: rect, options: [NSTrackingArea.Options.activeAlways, NSTrackingArea.Options.mouseEnteredAndExited, NSTrackingArea.Options.activeInActiveApp], owner: self, userInfo: ["menu": self.type]) - self.addTrackingArea(trackingArea) + let rect = NSRect(x: Constants.Widget.spacing, y: 0, width: container.frame.width, height: self.frame.height) + self.addTrackingArea(NSTrackingArea( + rect: rect, + options: [NSTrackingArea.Options.activeAlways, NSTrackingArea.Options.mouseEnteredAndExited, NSTrackingArea.Options.activeInActiveApp], + owner: self, + userInfo: nil + )) + + NSLayoutConstraint.activate([ + self.widthAnchor.constraint(equalTo: self.widget.pointee.preview.widthAnchor, constant: self.size), + self.heightAnchor.constraint(equalToConstant: self.size) + ]) + } + + private func button() -> NSView { + let button = NSButton(frame: NSRect(x: 0, y: 0, width: self.size, height: self.size)) + button.title = LocalizedString("Open widget settings") + button.toolTip = LocalizedString("Open widget settings") + button.bezelStyle = .regularSquare + if let image = Bundle(for: type(of: self)).image(forResource: "widget_settings") { + button.image = image + } + button.imageScaling = .scaleProportionallyDown + button.contentTintColor = .lightGray + button.isBordered = false + button.action = #selector(self.toggleSettings) + button.target = self + button.focusRingType = .none + + NSLayoutConstraint.activate([ + button.widthAnchor.constraint(equalToConstant: button.frame.width), + ]) + + return button + } + + private func separator() -> NSView { + let separator = NSView() + separator.widthAnchor.constraint(equalToConstant: 1).isActive = true + separator.wantsLayer = true + separator.layer?.backgroundColor = NSColor(hexString: "#dddddd").cgColor + + NSLayoutConstraint.activate([ + separator.heightAnchor.constraint(equalToConstant: self.size), + ]) + + return separator } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + @objc private func toggleSettings() { + self.settingsCallback(self.widget.pointee.type) + } + override func mouseEntered(with: NSEvent) { self.layer?.borderColor = NSColor.systemBlue.cgColor NSCursor.pointingHand.set() } override func mouseExited(with: NSEvent) { - self.layer?.borderColor = self.state ? NSColor.systemBlue.cgColor : NSColor.tertiaryLabelColor.cgColor + self.layer?.borderColor = self.widget.pointee.isActive ? NSColor.systemBlue.cgColor : NSColor.tertiaryLabelColor.cgColor NSCursor.arrow.set() } override func mouseDown(with: NSEvent) { - if !self.state { - NotificationCenter.default.post(name: .switchWidget, object: nil, userInfo: ["module": self.title, "widget": self.type.rawValue]) - } - } - - @objc private func maybeActivate(_ notification: Notification) { - if let moduleName = notification.userInfo?["module"] as? String { - if moduleName == self.title { - if let widgetName = notification.userInfo?["widget"] as? String { - if widgetName == self.type.rawValue { - self.layer?.borderColor = NSColor.systemBlue.cgColor - self.state = true - } else { - self.layer?.borderColor = NSColor.tertiaryLabelColor.cgColor - self.state = false - } - } - } - } + self.widget.pointee.toggle() + self.stateCallback() } } diff --git a/ModuleKit/widget.swift b/ModuleKit/widget.swift index f21ac1e2..64cc90aa 100644 --- a/ModuleKit/widget.swift +++ b/ModuleKit/widget.swift @@ -10,6 +10,7 @@ // import Cocoa +import os.log import StatsKit public enum widget_t: String { @@ -24,37 +25,64 @@ public enum widget_t: String { case sensors = "sensors" case memory = "memory" - public func new(module: String, config: NSDictionary?, store: UnsafePointer?, preview: Bool = false) -> Widget_p? { - var widget: Widget_p? = nil + public func new(module: String, config: NSDictionary?, store: UnsafePointer?) -> Widget? { + var widget: Widget? = nil let widgetConfig: NSDictionary? = config?[self.rawValue] as? NSDictionary switch self { case .mini: - widget = Mini(preview: preview, title: module, config: widgetConfig, store: store) + widget = Widget(self, module: module, + preview: Mini(title: module, config: widgetConfig, store: store, preview: true), + item: Mini(title: module, config: widgetConfig, store: store) + ) break case .lineChart: - widget = LineChart(preview: preview, title: module, config: widgetConfig, store: store) + widget = Widget(self, module: module, + preview: LineChart(title: module, config: widgetConfig, store: store, preview: true), + item: LineChart(title: module, config: widgetConfig, store: store) + ) break case .barChart: - widget = BarChart(preview: preview, title: module, config: widgetConfig, store: store) + widget = Widget(self, module: module, + preview: BarChart(title: module, config: widgetConfig, store: store, preview: true), + item: BarChart(title: module, config: widgetConfig, store: store) + ) break case .pieChart: - widget = PieChart(preview: preview, title: module, config: widgetConfig, store: store) + widget = Widget(self, module: module, + preview: PieChart(title: module, config: widgetConfig, store: store, preview: true), + item: PieChart(title: module, config: widgetConfig, store: store) + ) break case .networkChart: - widget = NetworkChart(preview: preview, title: module, config: widgetConfig, store: store) + widget = Widget(self, module: module, + preview: NetworkChart(title: module, config: widgetConfig, store: store, preview: true), + item: NetworkChart(title: module, config: widgetConfig, store: store) + ) break case .speed: - widget = SpeedWidget(preview: preview, title: module, config: widgetConfig, store: store) + widget = Widget(self, module: module, + preview: SpeedWidget(title: module, config: widgetConfig, store: store, preview: true), + item: SpeedWidget(title: module, config: widgetConfig, store: store) + ) break case .battery: - widget = BatterykWidget(preview: preview, title: module, config: widgetConfig, store: store) + widget = Widget(self, module: module, + preview: BatterykWidget(title: module, config: widgetConfig, store: store, preview: true), + item: BatterykWidget(title: module, config: widgetConfig, store: store) + ) break case .sensors: - widget = SensorsWidget(preview: preview, title: module, config: widgetConfig, store: store) + widget = Widget(self, module: module, + preview: SensorsWidget(title: module, config: widgetConfig, store: store, preview: true), + item: SensorsWidget(title: module, config: widgetConfig, store: store) + ) break case .memory: - widget = MemoryWidget(preview: preview, title: module, config: widgetConfig, store: store) + widget = Widget(self, module: module, + preview: MemoryWidget(title: module, config: widgetConfig, store: store, preview: true), + item: MemoryWidget(title: module, config: widgetConfig, store: store) + ) break default: break } @@ -79,27 +107,25 @@ public enum widget_t: String { } extension widget_t: CaseIterable {} -public protocol Widget_p: NSView { +public protocol widget_p: NSView { var type: widget_t { get } var title: String { get } var widthHandler: ((CGFloat) -> Void)? { get set } func setValues(_ values: [value_t]) - func settings(superview: NSView) + func settings(width: CGFloat) -> NSView } -open class Widget: NSView, Widget_p { +open class WidgetWrapper: NSView, widget_p { public var type: widget_t public var title: String public var widthHandler: ((CGFloat) -> Void)? = nil - private var widthHandlerRetry: Int8 = 0 - open override var intrinsicContentSize: CGSize { - return CGSize(width: self.frame.size.width, height: self.frame.size.height) - } - public init(_ type: widget_t, title: String, frame: NSRect, preview: Bool) { + private var widthHandlerRetry: Int8 = 0 + + public init(_ type: widget_t, title: String, frame: NSRect) { self.type = type self.title = title @@ -134,6 +160,101 @@ open class Widget: NSView, Widget_p { // MARK: - stubs - open func settings(superview: NSView) {} + open func settings(width: CGFloat) -> NSView { return NSView() } open func setValues(_ values: [value_t]) {} } + +public class Widget { + public let type: widget_t + public let module: String + public let preview: widget_p + public let item: widget_p + + public var isActive: Bool { + get { + let arr = Store.shared.string(key: "\(self.module)_widget", defaultValue: "").split(separator: ",") + return arr.contains{ $0 == self.type.rawValue } + } + set { + var arr = Store.shared.string(key: "\(self.module)_widget", defaultValue: "").split(separator: ",").map{ String($0) } + + if newValue { + arr.append(self.type.rawValue) + } else { + arr.removeAll{ $0 == self.type.rawValue } + } + + Store.shared.set(key: "\(self.module)_widget", value: arr.joined(separator: ",")) + } + } + + private var menuBarItem: NSStatusItem? = nil + private let log: OSLog + + public init(_ type: widget_t, module: String, preview: widget_p, item: widget_p) { + self.type = type + self.module = module + self.preview = preview + self.item = item + self.log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: self.module) + + self.item.widthHandler = { [weak self] value in + if let s = self { + s.menuBarItem?.length = value + os_log(.debug, log: s.log, "Widget %s change width to %.2f", "\(s.type)", value) + } + } + } + + // show item in the menu bar + public func enable() { + guard self.isActive else { + return + } + + let item = NSStatusBar.system.statusItem(withLength: self.item.frame.width) + item.autosaveName = "\(self.module)_\(self.type.name())" + item.isVisible = true + item.button?.target = self + item.button?.action = #selector(self.togglePopup) + item.button?.sendAction(on: [.leftMouseDown, .rightMouseDown]) + item.button?.addSubview(self.item) + + self.menuBarItem = item + + os_log(.debug, log: log, "Widget %s enabled", self.type.rawValue) + } + + // remove item from the menu bar + public func disable() { + if let item = self.menuBarItem { + item.length = 0 + item.isVisible = false + + os_log(.debug, log: log, "Widget %s disabled", self.type.rawValue) + } + } + + // toggle the widget + public func toggle() { + self.isActive = !self.isActive + + if !self.isActive { + self.disable() + } else { + self.enable() + } + + NotificationCenter.default.post(name: .toggleWidget, object: nil, userInfo: ["module": self.module]) + } + + @objc private func togglePopup(_ sender: Any) { + if let window = self.menuBarItem?.button?.window { + NotificationCenter.default.post(name: .togglePopup, object: nil, userInfo: [ + "module": self.module, + "origin": window.frame.origin, + "center": window.frame.width/2, + ]) + } + } +} diff --git a/Modules/Battery/main.swift b/Modules/Battery/main.swift index a14f08a9..255acbba 100644 --- a/Modules/Battery/main.swift +++ b/Modules/Battery/main.swift @@ -76,9 +76,6 @@ public class Battery: Module { } } - self.usageReader?.readyCallback = { [unowned self] in - self.readyHandler() - } self.usageReader?.callbackHandler = { [unowned self] value in self.usageCallback(value) } @@ -103,26 +100,27 @@ public class Battery: Module { return sources.count > 0 } - private func usageCallback(_ value: Battery_Usage?) { - if value == nil { + private func usageCallback(_ raw: Battery_Usage?) { + guard let value = raw else { return } - self.checkNotification(value: value!) - self.popupView.usageCallback(value!) - if let widget = self.widget as? Mini { - widget.setValue(abs(value!.level)) - } - if let widget = self.widget as? BarChart { - widget.setValue([value!.level]) - } - if let widget = self.widget as? BatterykWidget { - widget.setValue( - percentage: value?.level ?? 0, - ACStatus: value?.powerSource != "Battery Power", - isCharging: value?.isCharging ?? false, - time: (value?.timeToEmpty == 0 && value?.timeToCharge != 0 ? value?.timeToCharge : value?.timeToEmpty) ?? 0 - ) + self.checkNotification(value: value) + self.popupView.usageCallback(value) + + self.widgets.filter{ $0.isActive }.forEach { (w: Widget) in + switch w.item { + case let widget as Mini: widget.setValue(abs(value.level)) + case let widget as BarChart: widget.setValue([value.level]) + case let widget as BatterykWidget: + widget.setValue( + percentage: value.level , + ACStatus: value.powerSource != "Battery Power", + isCharging: value.isCharging , + time: value.timeToEmpty == 0 && value.timeToCharge != 0 ? value.timeToCharge : value.timeToEmpty + ) + default: break + } } } diff --git a/Modules/Battery/popup.swift b/Modules/Battery/popup.swift index c88c7c4a..cc7d52d9 100644 --- a/Modules/Battery/popup.swift +++ b/Modules/Battery/popup.swift @@ -224,7 +224,7 @@ internal class Popup: NSView, Popup_p { self.dashboardBatteryView?.setValue(abs(value.level)) self.levelField?.stringValue = "\(Int(abs(value.level) * 100)) %" - self.sourceField?.stringValue = "\(LocalizedString(value.powerSource))" + self.sourceField?.stringValue = LocalizedString(value.powerSource) self.timeField?.stringValue = "" if value.powerSource == "Battery Power" { diff --git a/Modules/Battery/settings.swift b/Modules/Battery/settings.swift index 687ca35b..66a74788 100644 --- a/Modules/Battery/settings.swift +++ b/Modules/Battery/settings.swift @@ -51,11 +51,11 @@ internal class Settings: NSView, Settings_v { fatalError("init(coder:) has not been implemented") } - public func load(widget: widget_t) { + public func load(widgets: [widget_t]) { self.subviews.forEach{ $0.removeFromSuperview() } let rowHeight: CGFloat = 30 - let num: CGFloat = widget == .battery ? 3 : 2 + let num: CGFloat = widgets.filter{ $0 == .battery }.isEmpty ? 2 : 3 let height: CGFloat = ((rowHeight + Constants.Settings.margin) * num) + Constants.Settings.margin let levels: [String] = self.levelsList.map { (v: String) -> String in @@ -91,7 +91,7 @@ internal class Settings: NSView, Settings_v { selected: "\(self.numberOfProcesses)" )) - if widget == .battery { + if !widgets.filter({ $0 == .battery }).isEmpty { self.addSubview(SelectRow( frame: NSRect( x: Constants.Settings.margin, diff --git a/Modules/CPU/main.swift b/Modules/CPU/main.swift index 8d481d07..4f104aee 100644 --- a/Modules/CPU/main.swift +++ b/Modules/CPU/main.swift @@ -78,9 +78,6 @@ public class CPU: Module { self.loadReader?.setInterval(value) } - self.loadReader?.readyCallback = { [unowned self] in - self.readyHandler() - } self.loadReader?.callbackHandler = { [unowned self] value in self.loadCallback(value) } @@ -116,27 +113,25 @@ public class CPU: Module { } } - private func loadCallback(_ value: CPU_Load?) { - guard value != nil else { + private func loadCallback(_ raw: CPU_Load?) { + guard let value = raw else { return } - self.popupView.loadCallback(value!) + self.popupView.loadCallback(value) - if let widget = self.widget as? Mini { - widget.setValue(value!.totalUsage) - } - if let widget = self.widget as? LineChart { - widget.setValue(value!.totalUsage) - } - if let widget = self.widget as? BarChart { - widget.setValue(self.usagePerCoreState ? value!.usagePerCore : [value!.totalUsage]) - } - if let widget = self.widget as? PieChart { - widget.setValue([ - circle_segment(value: value!.systemLoad, color: NSColor.systemRed), - circle_segment(value: value!.userLoad, color: NSColor.systemBlue) - ]) + self.widgets.filter{ $0.isActive }.forEach { (w: Widget) in + switch w.item { + case let widget as Mini: widget.setValue(value.totalUsage) + case let widget as LineChart: widget.setValue(value.totalUsage) + case let widget as BarChart: widget.setValue(self.usagePerCoreState ? value.usagePerCore : [value.totalUsage]) + case let widget as PieChart: + widget.setValue([ + circle_segment(value: value.systemLoad, color: NSColor.systemRed), + circle_segment(value: value.userLoad, color: NSColor.systemBlue) + ]) + default: break + } } } } diff --git a/Modules/CPU/settings.swift b/Modules/CPU/settings.swift index 3038e08a..3a1eb818 100644 --- a/Modules/CPU/settings.swift +++ b/Modules/CPU/settings.swift @@ -56,11 +56,11 @@ internal class Settings: NSView, Settings_v { fatalError("init(coder:) has not been implemented") } - public func load(widget: widget_t) { + public func load(widgets: [widget_t]) { self.subviews.forEach{ $0.removeFromSuperview() } let rowHeight: CGFloat = 30 - let num: CGFloat = widget == .barChart ? self.hasHyperthreadingCores ? 3 : 2 : 1 + let num: CGFloat = !widgets.filter{ $0 == .barChart }.isEmpty ? self.hasHyperthreadingCores ? 3 : 2 : 1 self.addSubview(SelectTitleRow( frame: NSRect(x: Constants.Settings.margin, y: Constants.Settings.margin + (rowHeight + Constants.Settings.margin) * num, width: self.frame.width - (Constants.Settings.margin*2), height: rowHeight), @@ -70,7 +70,7 @@ internal class Settings: NSView, Settings_v { selected: "\(self.updateIntervalValue) sec" )) - if widget == .barChart { + if !widgets.filter({ $0 == .barChart }).isEmpty { self.addSubview(ToggleTitleRow( frame: NSRect(x: Constants.Settings.margin, y: Constants.Settings.margin + (rowHeight + Constants.Settings.margin) * (num-1), width: self.frame.width - (Constants.Settings.margin*2), height: rowHeight), title: LocalizedString("Show usage per core"), diff --git a/Modules/Disk/main.swift b/Modules/Disk/main.swift index ce3d24fd..bde0ae99 100644 --- a/Modules/Disk/main.swift +++ b/Modules/Disk/main.swift @@ -105,9 +105,6 @@ public class Disk: Module { self.capacityReader?.store = store self.selectedDisk = store.pointee.string(key: "\(self.config.name)_disk", defaultValue: self.selectedDisk) - self.capacityReader?.readyCallback = { [unowned self] in - self.readyHandler() - } self.capacityReader?.callbackHandler = { [unowned self] value in self.capacityCallback(value) } @@ -156,17 +153,14 @@ public class Disk: Module { } let percentage = Double(usedSpace) / Double(total) - if let widget = self.widget as? Mini { - widget.setValue(percentage) - } - if let widget = self.widget as? BarChart { - widget.setValue([percentage]) - } - if let widget = self.widget as? MemoryWidget { - widget.setValue((DiskSize(free).getReadableMemory(), DiskSize(usedSpace).getReadableMemory())) - } - if let widget = self.widget as? SpeedWidget { - widget.setValue(upload: d.stats?.write ?? 0, download: d.stats?.read ?? 0) + self.widgets.filter{ $0.isActive }.forEach { (w: Widget) in + switch w.item { + case let widget as Mini: widget.setValue(percentage) + case let widget as BarChart: widget.setValue([percentage]) + case let widget as MemoryWidget: widget.setValue((DiskSize(free).getReadableMemory(), DiskSize(usedSpace).getReadableMemory())) + case let widget as SpeedWidget: widget.setValue(upload: d.stats?.write ?? 0, download: d.stats?.read ?? 0) + default: break + } } } } diff --git a/Modules/Disk/settings.swift b/Modules/Disk/settings.swift index 489f4873..b1e8fb7c 100644 --- a/Modules/Disk/settings.swift +++ b/Modules/Disk/settings.swift @@ -49,22 +49,20 @@ internal class Settings: NSView, Settings_v { fatalError("init(coder:) has not been implemented") } - public func load(widget: widget_t) { + public func load(widgets: [widget_t]) { self.subviews.forEach{ $0.removeFromSuperview() } let rowHeight: CGFloat = 30 - let num: CGFloat = widget != .speed ? 3 : 2 + let num: CGFloat = 3 - if widget != .speed { - self.intervalSelectView = SelectTitleRow( - frame: NSRect(x: Constants.Settings.margin, y: Constants.Settings.margin + (rowHeight + Constants.Settings.margin) * 2, width: self.frame.width - (Constants.Settings.margin*2), height: rowHeight), - title: LocalizedString("Update interval"), - action: #selector(changeUpdateInterval), - items: ReaderUpdateIntervals.map{ "\($0) sec" }, - selected: "\(self.updateIntervalValue) sec" - ) + self.intervalSelectView = SelectTitleRow( + frame: NSRect(x: Constants.Settings.margin, y: Constants.Settings.margin + (rowHeight + Constants.Settings.margin) * 2, width: self.frame.width - (Constants.Settings.margin*2), height: rowHeight), + title: LocalizedString("Update interval"), + action: #selector(changeUpdateInterval), + items: ReaderUpdateIntervals.map{ "\($0) sec" }, + selected: "\(self.updateIntervalValue) sec" + ) self.addSubview(self.intervalSelectView!) - } self.addDiskSelector() diff --git a/Modules/Fans/main.swift b/Modules/Fans/main.swift index 631c9f12..66d40e31 100644 --- a/Modules/Fans/main.swift +++ b/Modules/Fans/main.swift @@ -65,9 +65,6 @@ public class Fans: Module { self.fansReader.setInterval(value) } - self.fansReader.readyCallback = { [unowned self] in - self.readyHandler() - } self.fansReader.callbackHandler = { [unowned self] value in self.usageCallback(value) } @@ -85,24 +82,27 @@ public class Fans: Module { } } - private func usageCallback(_ value: [Fan]?) { - if value == nil { + private func usageCallback(_ raw: [Fan]?) { + guard let value = raw else { return } - self.popupView.usageCallback(value!) + self.popupView.usageCallback(value) let label: Bool = store.pointee.bool(key: "Fans_label", defaultValue: false) var list: [KeyValue_t] = [] - value!.forEach { (f: Fan) in + value.forEach { (f: Fan) in if f.state { let str = label ? "\(f.name.prefix(1).uppercased()): \(f.formattedValue)" : f.formattedValue list.append(KeyValue_t(key: "Fan#\(f.id)", value: str)) } } - if let widget = self.widget as? SensorsWidget { - widget.setValues(list) + self.widgets.filter{ $0.isActive }.forEach { (w: Widget) in + switch w.item { + case let widget as SensorsWidget: widget.setValues(list) + default: break + } } } } diff --git a/Modules/Fans/settings.swift b/Modules/Fans/settings.swift index 673cefbd..6852447e 100644 --- a/Modules/Fans/settings.swift +++ b/Modules/Fans/settings.swift @@ -48,7 +48,7 @@ internal class Settings: NSView, Settings_v { fatalError("init(coder:) has not been implemented") } - func load(widget: widget_t) { + func load(widgets: [widget_t]) { guard !self.list.pointee.isEmpty else { return } diff --git a/Modules/GPU/main.swift b/Modules/GPU/main.swift index 3ab5b793..f9364a59 100644 --- a/Modules/GPU/main.swift +++ b/Modules/GPU/main.swift @@ -93,9 +93,6 @@ public class GPU: Module { self.infoReader?.smc = smc self.selectedGPU = store.pointee.string(key: "\(self.config.name)_gpu", defaultValue: self.selectedGPU) - self.infoReader?.readyCallback = { [unowned self] in - self.readyHandler() - } self.infoReader?.callbackHandler = { [unowned self] value in self.infoCallback(value) } @@ -135,15 +132,15 @@ public class GPU: Module { return } - if let widget = self.widget as? Mini { - widget.setValue(utilization) - widget.setTitle(self.showType ? "\(selectedGPU.type)GPU" : nil) - } - if let widget = self.widget as? LineChart { - widget.setValue(utilization) - } - if let widget = self.widget as? BarChart { - widget.setValue([utilization]) + self.widgets.filter{ $0.isActive }.forEach { (w: Widget) in + switch w.item { + case let widget as Mini: + widget.setValue(utilization) + widget.setTitle(self.showType ? "\(selectedGPU.type)GPU" : nil) + case let widget as LineChart: widget.setValue(utilization) + case let widget as BarChart: widget.setValue([utilization]) + default: break + } } } } diff --git a/Modules/GPU/settings.swift b/Modules/GPU/settings.swift index f9a21242..67cba3be 100644 --- a/Modules/GPU/settings.swift +++ b/Modules/GPU/settings.swift @@ -50,11 +50,11 @@ internal class Settings: NSView, Settings_v { fatalError("init(coder:) has not been implemented") } - public func load(widget: widget_t) { + public func load(widgets: [widget_t]) { self.subviews.forEach{ $0.removeFromSuperview() } let rowHeight: CGFloat = 30 - let num: CGFloat = widget == .mini ? 3 : 2 + let num: CGFloat = widgets.filter{ $0 == .mini }.isEmpty ? 2 : 3 self.addSubview(SelectTitleRow( frame: NSRect( @@ -69,7 +69,7 @@ internal class Settings: NSView, Settings_v { selected: "\(self.updateIntervalValue) sec" )) - if widget == .mini { + if !widgets.filter({ $0 == .mini }).isEmpty { self.addSubview(ToggleTitleRow( frame: NSRect( x: Constants.Settings.margin, diff --git a/Modules/Net/main.swift b/Modules/Net/main.swift index e7c117dd..c78ec24a 100644 --- a/Modules/Net/main.swift +++ b/Modules/Net/main.swift @@ -95,9 +95,6 @@ public class Network: Module { } } - self.usageReader?.readyCallback = { [unowned self] in - self.readyHandler() - } self.usageReader?.callbackHandler = { [unowned self] value in self.usageCallback(value) } @@ -131,16 +128,19 @@ public class Network: Module { return list.count > 0 } - private func usageCallback(_ value: Network_Usage?) { - guard let value = value else { + private func usageCallback(_ raw: Network_Usage?) { + guard let value = raw else { return } self.popupView.usageCallback(value) - if let widget = self.widget as? SpeedWidget { - widget.setValue(upload: value.bandwidth.upload, download: value.bandwidth.download) - } else if let widget = self.widget as? NetworkChart { - widget.setValue(upload: Double(value.bandwidth.upload), download: Double(value.bandwidth.download)) + + self.widgets.filter{ $0.isActive }.forEach { (w: Widget) in + switch w.item { + case let widget as SpeedWidget: widget.setValue(upload: value.bandwidth.upload, download: value.bandwidth.download) + case let widget as NetworkChart: widget.setValue(upload: Double(value.bandwidth.upload), download: Double(value.bandwidth.download)) + default: break + } } } } diff --git a/Modules/Net/settings.swift b/Modules/Net/settings.swift index 8c5dcfd6..e2f407bc 100644 --- a/Modules/Net/settings.swift +++ b/Modules/Net/settings.swift @@ -54,7 +54,7 @@ internal class Settings: NSView, Settings_v { fatalError("init(coder:) has not been implemented") } - public func load(widget: widget_t) { + public func load(widgets: [widget_t]) { self.subviews.forEach{ $0.removeFromSuperview() } let rowHeight: CGFloat = 30 diff --git a/Modules/RAM/main.swift b/Modules/RAM/main.swift index 2d9d4f65..73de5d97 100644 --- a/Modules/RAM/main.swift +++ b/Modules/RAM/main.swift @@ -81,9 +81,6 @@ public class RAM: Module { } } - self.usageReader?.readyCallback = { [unowned self] in - self.readyHandler() - } self.usageReader?.callbackHandler = { [unowned self] value in self.loadCallback(value) } @@ -108,30 +105,31 @@ public class RAM: Module { } self.popupView.loadCallback(value) - if let widget = self.widget as? Mini { - widget.setValue(value.usage) - widget.setPressure(value.pressureLevel) - } - if let widget = self.widget as? LineChart { - widget.setValue(value.usage) - widget.setPressure(value.pressureLevel) - } - if let widget = self.widget as? BarChart { - widget.setValue([value.usage]) - widget.setPressure(value.pressureLevel) - } - if let widget = self.widget as? PieChart { - let total: Double = value.total - widget.setValue([ - circle_segment(value: value.app/total, color: NSColor.systemBlue), - circle_segment(value: value.wired/total, color: NSColor.systemOrange), - circle_segment(value: value.compressed/total, color: NSColor.systemPink) - ]) - } - if let widget = self.widget as? MemoryWidget { - let free = Units(bytes: Int64(value.free)).getReadableMemory() - let used = Units(bytes: Int64(value.used)).getReadableMemory() - widget.setValue((free, used)) + + self.widgets.filter{ $0.isActive }.forEach { (w: Widget) in + switch w.item { + case let widget as Mini: + widget.setValue(value.usage) + widget.setPressure(value.pressureLevel) + case let widget as LineChart: + widget.setValue(value.usage) + widget.setPressure(value.pressureLevel) + case let widget as BarChart: + widget.setValue([value.usage]) + widget.setPressure(value.pressureLevel) + case let widget as PieChart: + let total: Double = value.total == 0 ? 1 : value.total + widget.setValue([ + circle_segment(value: value.app/total, color: NSColor.systemBlue), + circle_segment(value: value.wired/total, color: NSColor.systemOrange), + circle_segment(value: value.compressed/total, color: NSColor.systemPink) + ]) + case let widget as MemoryWidget: + let free = Units(bytes: Int64(value.free)).getReadableMemory() + let used = Units(bytes: Int64(value.used)).getReadableMemory() + widget.setValue((free, used)) + default: break + } } } } diff --git a/Modules/RAM/settings.swift b/Modules/RAM/settings.swift index fa996a38..98493d57 100644 --- a/Modules/RAM/settings.swift +++ b/Modules/RAM/settings.swift @@ -45,7 +45,7 @@ internal class Settings: NSView, Settings_v { fatalError("init(coder:) has not been implemented") } - public func load(widget: widget_t) { + public func load(widgets: [widget_t]) { self.subviews.forEach{ $0.removeFromSuperview() } let rowHeight: CGFloat = 30 diff --git a/Modules/Sensors/main.swift b/Modules/Sensors/main.swift index d68e008f..4ec7bc71 100644 --- a/Modules/Sensors/main.swift +++ b/Modules/Sensors/main.swift @@ -40,9 +40,6 @@ public class Sensors: Module { self.sensorsReader.setInterval(value) } - self.sensorsReader.readyCallback = { [unowned self] in - self.readyHandler() - } self.sensorsReader.callbackHandler = { [unowned self] value in self.usageCallback(value) } @@ -60,21 +57,25 @@ public class Sensors: Module { } } - private func usageCallback(_ value: [Sensor_t]?) { - if value == nil { + private func usageCallback(_ raw: [Sensor_t]?) { + guard let value = raw else { return } var list: [KeyValue_t] = [] - value!.forEach { (s: Sensor_t) in + value.forEach { (s: Sensor_t) in if s.state { list.append(KeyValue_t(key: s.key, value: s.formattedMiniValue)) } } - self.popupView.usageCallback(value!) - if let widget = self.widget as? SensorsWidget { - widget.setValues(list) + self.popupView.usageCallback(value) + + self.widgets.filter{ $0.isActive }.forEach { (w: Widget) in + switch w.item { + case let widget as SensorsWidget: widget.setValues(list) + default: break + } } } } diff --git a/Modules/Sensors/settings.swift b/Modules/Sensors/settings.swift index 30fd0349..b9cf7d72 100644 --- a/Modules/Sensors/settings.swift +++ b/Modules/Sensors/settings.swift @@ -45,7 +45,7 @@ internal class Settings: NSView, Settings_v { fatalError("init(coder:) has not been implemented") } - public func load(widget: widget_t) { + public func load(widgets: [widget_t]) { guard !self.list.pointee.isEmpty else { return } diff --git a/StatsKit/Charts.swift b/StatsKit/Charts.swift index 19702113..7643906a 100644 --- a/StatsKit/Charts.swift +++ b/StatsKit/Charts.swift @@ -177,7 +177,7 @@ public class NetworkChartView: NSView { context.saveGState() var underLinePath = uploadlinePath.copy() as! NSBezierPath - underLinePath.line(to: CGPoint(x: columnXPoint(points.count - 1), y: zero)) + underLinePath.line(to: CGPoint(x: columnXPoint(points.count), y: zero)) underLinePath.line(to: CGPoint(x: columnXPoint(0), y: zero)) underLinePath.close() underLinePath.addClip() @@ -188,7 +188,7 @@ public class NetworkChartView: NSView { context.saveGState() underLinePath = downloadlinePath.copy() as! NSBezierPath - underLinePath.line(to: CGPoint(x: columnXPoint(points.count - 1), y: zero)) + underLinePath.line(to: CGPoint(x: columnXPoint(points.count), y: zero)) underLinePath.line(to: CGPoint(x: columnXPoint(0), y: zero)) underLinePath.close() underLinePath.addClip() diff --git a/StatsKit/extensions.swift b/StatsKit/extensions.swift index f6acaf69..cb9ea518 100644 --- a/StatsKit/extensions.swift +++ b/StatsKit/extensions.swift @@ -387,6 +387,8 @@ public extension NSView { public extension Notification.Name { static let toggleSettings = Notification.Name("toggleSettings") static let toggleModule = Notification.Name("toggleModule") + static let togglePopup = Notification.Name("togglePopup") + static let toggleWidget = Notification.Name("toggleWidget") static let openModuleSettings = Notification.Name("openModuleSettings") static let settingsAppear = Notification.Name("settingsAppear") static let switchWidget = Notification.Name("switchWidget") @@ -398,12 +400,12 @@ public extension Notification.Name { public class NSButtonWithPadding: NSButton { public var horizontalPadding: CGFloat = 0 public var verticalPadding: CGFloat = 0 - + public override var intrinsicContentSize: NSSize { var size = super.intrinsicContentSize size.width += self.horizontalPadding size.height += self.verticalPadding - return size; + return size } } @@ -507,3 +509,52 @@ public extension CATransaction { CATransaction.commit() } } + +public final class FlippedClipView: NSClipView { + public override var isFlipped: Bool { + return true + } +} + +public final class ScrollableStackView: NSView { + public let stackView: NSStackView = NSStackView() + public let clipView: FlippedClipView = FlippedClipView() + private let scrollView: NSScrollView = NSScrollView() + + public override init(frame: NSRect) { + super.init(frame: frame) + + scrollView.translatesAutoresizingMaskIntoConstraints = false + scrollView.borderType = .noBorder + scrollView.hasVerticalScroller = true + scrollView.hasHorizontalScroller = false + scrollView.autohidesScrollers = true + scrollView.horizontalScrollElasticity = .none + scrollView.drawsBackground = false + self.addSubview(self.scrollView) + + NSLayoutConstraint.activate([ + scrollView.leftAnchor.constraint(equalTo: self.leftAnchor), + scrollView.rightAnchor.constraint(equalTo: self.rightAnchor), + scrollView.topAnchor.constraint(equalTo: self.topAnchor), + scrollView.bottomAnchor.constraint(equalTo: self.bottomAnchor), + ]) + + clipView.drawsBackground = false + scrollView.contentView = clipView + + stackView.orientation = .vertical + stackView.translatesAutoresizingMaskIntoConstraints = false + scrollView.documentView = stackView + + NSLayoutConstraint.activate([ + stackView.leftAnchor.constraint(equalTo: clipView.leftAnchor), + stackView.rightAnchor.constraint(equalTo: clipView.rightAnchor), + stackView.topAnchor.constraint(equalTo: clipView.topAnchor), + ]) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +}