diff --git a/Modules/Disk/main.swift b/Modules/Disk/main.swift index 5f7ec8c3..36b3c847 100644 --- a/Modules/Disk/main.swift +++ b/Modules/Disk/main.swift @@ -128,10 +128,12 @@ public class Disks { } public class Disk: Module { - private let popupView: Popup = Popup() + private let popupView: Popup + private let settingsView: Settings + private let portalView: Portal + private var capacityReader: CapacityReader? = nil private var activityReader: ActivityReader? = nil - private var settingsView: Settings private var selectedDisk: String = "" private var notificationLevelState: Bool = false private var notificationID: String? = nil @@ -143,11 +145,14 @@ public class Disk: Module { } public init() { + self.popupView = Popup() self.settingsView = Settings("Disk") + self.portalView = Portal("Disk") super.init( popup: self.popupView, - settings: self.settingsView + settings: self.settingsView, + portal: self.portalView ) guard self.available else { return } @@ -217,6 +222,7 @@ public class Disk: Module { } let percentage = Double(usedSpace) / Double(total) + self.portalView.loadCallback(percentage) self.checkNotificationLevel(percentage) self.menuBar.widgets.filter{ $0.isActive }.forEach { (w: Widget) in diff --git a/Modules/Disk/portal.swift b/Modules/Disk/portal.swift new file mode 100644 index 00000000..c05c8b40 --- /dev/null +++ b/Modules/Disk/portal.swift @@ -0,0 +1,66 @@ +// +// portal.swift +// Disk +// +// Created by Serhiy Mytrovtsiy on 20/02/2023 +// Using Swift 5.0 +// Running on macOS 13.2 +// +// Copyright © 2023 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import Kit + +internal class Portal: NSStackView, Portal_p { + var name: String + + private var circle: PieChartView? = nil + + private var initialized: Bool = false + + init(_ name: String) { + self.name = name + + super.init(frame: NSRect.zero) + + self.wantsLayer = true + self.layer?.backgroundColor = NSColor.windowBackgroundColor.cgColor + self.layer?.cornerRadius = 3 + + self.orientation = .horizontal + self.distribution = .fillEqually + self.edgeInsets = NSEdgeInsets( + top: Constants.Popup.margins, + left: Constants.Popup.margins, + bottom: Constants.Popup.margins, + right: Constants.Popup.margins + ) + + self.circle = PieChartView(frame: NSRect.zero, segments: [], drawValue: true) + self.circle!.toolTip = localizedString("Disk usage") + self.addArrangedSubview(self.circle!) + + self.heightAnchor.constraint(equalToConstant: Constants.Popup.portalHeight).isActive = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func updateLayer() { + self.layer?.backgroundColor = NSColor.windowBackgroundColor.cgColor + } + + public func loadCallback(_ value: Double) { + DispatchQueue.main.async(execute: { + if (self.window?.isVisible ?? false) || !self.initialized { + self.circle?.setValue(value) + self.circle?.setSegments([ + circle_segment(value: value, color: .controlAccentColor) + ]) + self.initialized = true + } + }) + } +} diff --git a/Modules/GPU/main.swift b/Modules/GPU/main.swift index 2bdc0f3f..60f71a13 100644 --- a/Modules/GPU/main.swift +++ b/Modules/GPU/main.swift @@ -65,9 +65,11 @@ public struct GPUs: value_t { } public class GPU: Module { + private let popupView: Popup + private let settingsView: Settings + private let portalView: Portal + private var infoReader: InfoReader? = nil - private var settingsView: Settings - private var popupView: Popup = Popup() private var selectedGPU: String = "" private var notificationLevelState: Bool = false @@ -85,11 +87,14 @@ public class GPU: Module { } public init() { + self.popupView = Popup() self.settingsView = Settings("GPU") + self.portalView = Portal("GPU") super.init( popup: self.popupView, - settings: self.settingsView + settings: self.settingsView, + portal: self.portalView ) guard self.available else { return } @@ -138,6 +143,7 @@ public class GPU: Module { return } + self.portalView.loadCallback(selectedGPU) self.checkNotificationLevel(utilization) self.menuBar.widgets.filter{ $0.isActive }.forEach { (w: Widget) in diff --git a/Modules/GPU/portal.swift b/Modules/GPU/portal.swift new file mode 100644 index 00000000..f12c53fe --- /dev/null +++ b/Modules/GPU/portal.swift @@ -0,0 +1,64 @@ +// +// portal.swift +// GPU +// +// Created by Serhiy Mytrovtsiy on 18/02/2023 +// Using Swift 5.0 +// Running on macOS 13.2 +// +// Copyright © 2023 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import Kit + +public class Portal: NSStackView, Portal_p { + public var name: String + + private var circle: HalfCircleGraphView? = nil + + private var initialized: Bool = false + + init(_ name: String) { + self.name = name + + super.init(frame: NSRect.zero) + + self.wantsLayer = true + self.layer?.backgroundColor = NSColor.windowBackgroundColor.cgColor + self.layer?.cornerRadius = 3 + + self.orientation = .horizontal + self.distribution = .fillEqually + self.edgeInsets = NSEdgeInsets( + top: Constants.Popup.margins, + left: Constants.Popup.margins, + bottom: 0, + right: Constants.Popup.margins + ) + + self.circle = HalfCircleGraphView() + self.circle!.toolTip = localizedString("GPU usage") + self.addArrangedSubview(self.circle!) + + self.heightAnchor.constraint(equalToConstant: Constants.Popup.portalHeight).isActive = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func updateLayer() { + self.layer?.backgroundColor = NSColor.windowBackgroundColor.cgColor + } + + public func loadCallback(_ value: GPU_Info) { + DispatchQueue.main.async(execute: { + if (self.window?.isVisible ?? false) || !self.initialized { + self.circle?.setValue(value.utilization!) + self.circle?.setText("\(Int(value.utilization!*100))%") + self.initialized = true + } + }) + } +} diff --git a/Modules/Net/main.swift b/Modules/Net/main.swift index eb1fb30f..0b748ef5 100644 --- a/Modules/Net/main.swift +++ b/Modules/Net/main.swift @@ -99,8 +99,9 @@ public struct Network_Process { } public class Network: Module { - private var popupView: Popup - private var settingsView: Settings + private let popupView: Popup + private let settingsView: Settings + private let portalView: Portal private var usageReader: UsageReader? = nil private var processReader: ProcessReader? = nil @@ -116,10 +117,12 @@ public class Network: Module { public init() { self.settingsView = Settings("Network") self.popupView = Popup("Network") + self.portalView = Portal("Network") super.init( popup: self.popupView, - settings: self.settingsView + settings: self.settingsView, + portal: self.portalView ) guard self.available else { return } @@ -195,6 +198,7 @@ public class Network: Module { } self.popupView.usageCallback(value) + self.portalView.usageCallback(value) var upload: Int64 = 0 var download: Int64 = 0 diff --git a/Modules/Net/portal.swift b/Modules/Net/portal.swift new file mode 100644 index 00000000..a44abf76 --- /dev/null +++ b/Modules/Net/portal.swift @@ -0,0 +1,174 @@ +// +// portal.swift +// Net +// +// Created by Serhiy Mytrovtsiy on 18/02/2023 +// Using Swift 5.0 +// Running on macOS 13.2 +// +// Copyright © 2023 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import Kit + +public class Portal: NSStackView, Portal_p { + public var name: String + + private var initialized: Bool = false + + private var uploadView: NSView? = nil + private var uploadValueField: NSTextField? = nil + private var uploadUnitField: NSTextField? = nil + private var uploadColorView: ColorView? = nil + + private var downloadView: NSView? = nil + private var downloadValueField: NSTextField? = nil + private var downloadUnitField: NSTextField? = nil + private var downloadColorView: ColorView? = nil + + private var base: DataSizeBase { + get { + DataSizeBase(rawValue: Store.shared.string(key: "\(self.name)_base", defaultValue: "byte")) ?? .byte + } + } + + private var downloadColorState: Color = .secondBlue + private var downloadColor: NSColor { + var value = NSColor.systemRed + if let color = self.downloadColorState.additional as? NSColor { + value = color + } + return value + } + private var uploadColorState: Color = .secondRed + private var uploadColor: NSColor { + var value = NSColor.systemBlue + if let color = self.uploadColorState.additional as? NSColor { + value = color + } + return value + } + + init(_ name: String) { + self.name = name + + super.init(frame: NSRect.zero) + + self.wantsLayer = true + self.layer?.backgroundColor = NSColor.windowBackgroundColor.cgColor + self.layer?.cornerRadius = 3 + + self.orientation = .horizontal + self.distribution = .fillEqually + self.spacing = 0 + self.edgeInsets = NSEdgeInsets( + top: 0, + left: Constants.Popup.margins, + bottom: Constants.Popup.margins, + right: 0 + ) + + let container = NSStackView() + container.widthAnchor.constraint(equalToConstant: (Constants.Popup.width/2) - (Constants.Popup.margins*2)).isActive = true + container.spacing = Constants.Popup.spacing + container.orientation = .vertical + container.distribution = .fillEqually + container.spacing = 0 + + let (uView, uField, uUnit, uColor) = self.IOView(operation: localizedString("Uploading"), color: self.uploadColor) + let (dView, dField, dUnit, dColor) = self.IOView(operation: localizedString("Downloading"), color: self.downloadColor) + + self.uploadValueField = uField + self.uploadUnitField = uUnit + self.uploadColorView = uColor + + self.downloadValueField = dField + self.downloadUnitField = dUnit + self.downloadColorView = dColor + + container.addArrangedSubview(uView) + container.addArrangedSubview(dView) + + self.addArrangedSubview(NSView()) + self.addArrangedSubview(container) + self.addArrangedSubview(NSView()) + + self.heightAnchor.constraint(equalToConstant: Constants.Popup.portalHeight).isActive = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func updateLayer() { + self.layer?.backgroundColor = NSColor.windowBackgroundColor.cgColor + } + + private func IOView(operation: String, color: NSColor) -> (NSView, NSTextField, NSTextField, ColorView) { + let box = NSStackView() + box.orientation = .vertical + box.spacing = Constants.Popup.spacing + box.translatesAutoresizingMaskIntoConstraints = false + + let value = NSStackView() + value.orientation = .horizontal + value.spacing = 0 + value.alignment = .bottom + + let valueField = LabelField("0") + valueField.font = NSFont.systemFont(ofSize: 24, weight: .light) + valueField.textColor = .textColor + valueField.alignment = .right + + let unitField = LabelField("KB/s") + unitField.heightAnchor.constraint(equalToConstant: 18).isActive = true + unitField.font = NSFont.systemFont(ofSize: 13, weight: .light) + unitField.textColor = .labelColor + unitField.alignment = .left + + value.addArrangedSubview(valueField) + value.addArrangedSubview(unitField) + + let title = NSStackView() + title.orientation = .horizontal + title.spacing = 0 + title.alignment = .centerY + + let colorBlock: ColorView = ColorView(color: color, radius: 3) + colorBlock.widthAnchor.constraint(equalToConstant: 10).isActive = true + colorBlock.heightAnchor.constraint(equalToConstant: 10).isActive = true + + let titleField = LabelField(operation) + titleField.font = NSFont.systemFont(ofSize: 11, weight: .light) + titleField.alignment = .center + + title.addArrangedSubview(colorBlock) + title.addArrangedSubview(titleField) + + box.addArrangedSubview(value) + box.addArrangedSubview(title) + + return (box, valueField, unitField, colorBlock) + } + + public func usageCallback(_ value: Network_Usage) { + DispatchQueue.main.async(execute: { + if (self.window?.isVisible ?? false) || !self.initialized { + let upload = Units(bytes: value.bandwidth.upload).getReadableTuple(base: self.base) + let download = Units(bytes: value.bandwidth.download).getReadableTuple(base: self.base) + + self.uploadValueField?.stringValue = "\(upload.0)" + self.uploadUnitField?.stringValue = upload.1 + + self.downloadValueField?.stringValue = "\(download.0)" + self.downloadUnitField?.stringValue = download.1 + + self.uploadColorView?.setState(value.bandwidth.upload != 0) + self.downloadColorView?.setState(value.bandwidth.download != 0) + + self.initialized = true + } + }) + } +} diff --git a/Stats.xcodeproj/project.pbxproj b/Stats.xcodeproj/project.pbxproj index 523d9d8c..48ec7409 100644 --- a/Stats.xcodeproj/project.pbxproj +++ b/Stats.xcodeproj/project.pbxproj @@ -9,6 +9,12 @@ /* Begin PBXBuildFile section */ 5C0A2A8A292A5B4D009B4C1F /* SMJobBlessUtil.py in Resources */ = {isa = PBXBuildFile; fileRef = 5C0A2A89292A5B4D009B4C1F /* SMJobBlessUtil.py */; }; 5C21D80B296C7B81005BA16D /* CombinedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C21D80A296C7B81005BA16D /* CombinedView.swift */; }; + 5C23BC0229A0102500DBA990 /* portal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C23BC0129A0102500DBA990 /* portal.swift */; }; + 5C23BC0429A014AC00DBA990 /* portal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C23BC0329A014AC00DBA990 /* portal.swift */; }; + 5C23BC0829A03D1200DBA990 /* portal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C23BC0729A03D1200DBA990 /* portal.swift */; }; + 5C23BC0A29A0EDA300DBA990 /* portal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C23BC0929A0EDA300DBA990 /* portal.swift */; }; + 5C23BC0C29A10BE000DBA990 /* portal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C23BC0B29A10BE000DBA990 /* portal.swift */; }; + 5C23BC1029A3B5AE00DBA990 /* portal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C23BC0F29A3B5AE00DBA990 /* portal.swift */; }; 5C8E001029269C7F0027C75A /* protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE493829265055000F2856 /* protocol.swift */; }; 5CFE492A29264DF1000F2856 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE492929264DF1000F2856 /* main.swift */; }; 5CFE493929265055000F2856 /* protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE493829265055000F2856 /* protocol.swift */; }; @@ -348,6 +354,12 @@ 4921436D25319699000A1C47 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; 5C0A2A89292A5B4D009B4C1F /* SMJobBlessUtil.py */ = {isa = PBXFileReference; lastKnownFileType = text.script.python; path = SMJobBlessUtil.py; sourceTree = ""; }; 5C21D80A296C7B81005BA16D /* CombinedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombinedView.swift; sourceTree = ""; }; + 5C23BC0129A0102500DBA990 /* portal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = portal.swift; sourceTree = ""; }; + 5C23BC0329A014AC00DBA990 /* portal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = portal.swift; sourceTree = ""; }; + 5C23BC0729A03D1200DBA990 /* portal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = portal.swift; sourceTree = ""; }; + 5C23BC0929A0EDA300DBA990 /* portal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = portal.swift; sourceTree = ""; }; + 5C23BC0B29A10BE000DBA990 /* portal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = portal.swift; sourceTree = ""; }; + 5C23BC0F29A3B5AE00DBA990 /* portal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = portal.swift; sourceTree = ""; }; 5CFE492729264DF1000F2856 /* eu.exelban.Stats.SMC.Helper */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = eu.exelban.Stats.SMC.Helper; sourceTree = BUILT_PRODUCTS_DIR; }; 5CFE492929264DF1000F2856 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 5CFE493829265055000F2856 /* protocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = protocol.swift; sourceTree = ""; }; @@ -743,6 +755,7 @@ 9A2847782666AA5000EC1F6D /* widget.swift */, 9A2847772666AA5000EC1F6D /* reader.swift */, 9A2847752666AA5000EC1F6D /* settings.swift */, + 5C23BC0329A014AC00DBA990 /* portal.swift */, ); path = module; sourceTree = ""; @@ -763,6 +776,7 @@ 9A3E17D8247A94B500449CD1 /* main.swift */, 9A3E17DA247A94BC00449CD1 /* readers.swift */, 9A3E17E9247B07BF00449CD1 /* popup.swift */, + 5C23BC0B29A10BE000DBA990 /* portal.swift */, 9A58DEA324B3647600716A9F /* settings.swift */, 9A3E17CF247A94AF00449CD1 /* Info.plist */, 9A3E17DC247A94C300449CD1 /* config.plist */, @@ -812,6 +826,7 @@ 9A81C7672449A43600825D92 /* main.swift */, 9A81C7682449A43600825D92 /* readers.swift */, 9AA6425F244B274200416A33 /* popup.swift */, + 5C23BC0729A03D1200DBA990 /* portal.swift */, 9A953A1324B9D22D0038EF4B /* settings.swift */, 9A81C7592449A41400825D92 /* Info.plist */, 9AF9EE192464A7B3005D2270 /* config.plist */, @@ -825,6 +840,7 @@ 9A90E19524EAD35F00471E9A /* main.swift */, 9A90E1A224EAD66600471E9A /* reader.swift */, 9A53EBFA24EB041E00648841 /* popup.swift */, + 5C23BC0929A0EDA300DBA990 /* portal.swift */, 9A53EBF824EAFA5200648841 /* settings.swift */, 9A90E18C24EAD2BB00471E9A /* Info.plist */, 9A90E19724EAD3B000471E9A /* config.plist */, @@ -838,6 +854,7 @@ 9A97CEE82537338600742D8F /* main.swift */, 9A97CEF0253733D200742D8F /* readers.swift */, 9A97CEF5253733E400742D8F /* popup.swift */, + 5C23BC0129A0102500DBA990 /* portal.swift */, 9A97CEFA253733F300742D8F /* settings.swift */, 9A97CECD2537331B00742D8F /* Info.plist */, 9A97CEFF2537340400742D8F /* config.plist */, @@ -939,6 +956,7 @@ 9AF9EE0E2464875F005D2270 /* main.swift */, 9AF9EE1024648ADC005D2270 /* readers.swift */, 9A5AF11A2469CE9B00684737 /* popup.swift */, + 5C23BC0F29A3B5AE00DBA990 /* portal.swift */, 9AB7FD7B246B48DB00387FDA /* settings.swift */, 9AF9EE0524648751005D2270 /* Info.plist */, 9AF9EE12246492E8005D2270 /* config.plist */, @@ -1622,6 +1640,7 @@ 9A2847632666AA2700EC1F6D /* Mini.swift in Sources */, 9A6EEBBE2685259500897371 /* Logger.swift in Sources */, 9A2847602666AA2700EC1F6D /* NetworkChart.swift in Sources */, + 5C23BC0429A014AC00DBA990 /* portal.swift in Sources */, 9A2847792666AA5000EC1F6D /* module.swift in Sources */, 9A2847662666AA2700EC1F6D /* Speed.swift in Sources */, 9A2847682666AA2700EC1F6D /* Sensors.swift in Sources */, @@ -1643,6 +1662,7 @@ files = ( 9A3E17DB247A94BC00449CD1 /* readers.swift in Sources */, 9A3E17EA247B07BF00449CD1 /* popup.swift in Sources */, + 5C23BC0C29A10BE000DBA990 /* portal.swift in Sources */, 9A58DEA424B3647600716A9F /* settings.swift in Sources */, 9A3E17D9247A94B500449CD1 /* main.swift in Sources */, ); @@ -1654,6 +1674,7 @@ files = ( 9A81C76A2449A43600825D92 /* readers.swift in Sources */, 9AA64260244B274200416A33 /* popup.swift in Sources */, + 5C23BC0829A03D1200DBA990 /* portal.swift in Sources */, 9A953A1424B9D22D0038EF4B /* settings.swift in Sources */, 9A81C7692449A43600825D92 /* main.swift in Sources */, ); @@ -1666,6 +1687,7 @@ 9A46C06B266D8602001A1117 /* smc.swift in Sources */, 9A90E1A324EAD66600471E9A /* reader.swift in Sources */, 9A90E19624EAD35F00471E9A /* main.swift in Sources */, + 5C23BC0A29A0EDA300DBA990 /* portal.swift in Sources */, 9A53EBFB24EB041E00648841 /* popup.swift in Sources */, 9A53EBF924EAFA5200648841 /* settings.swift in Sources */, ); @@ -1678,6 +1700,7 @@ 9A46C077266D8606001A1117 /* smc.swift in Sources */, 9A97CEF1253733D200742D8F /* readers.swift in Sources */, 9A97CEF6253733E400742D8F /* popup.swift in Sources */, + 5C23BC0229A0102500DBA990 /* portal.swift in Sources */, 9A97CEFB253733F300742D8F /* settings.swift in Sources */, 9A97CEE92537338600742D8F /* main.swift in Sources */, ); @@ -1731,6 +1754,7 @@ files = ( 9A5AF11B2469CE9B00684737 /* popup.swift in Sources */, 9AF9EE1124648ADC005D2270 /* readers.swift in Sources */, + 5C23BC1029A3B5AE00DBA990 /* portal.swift in Sources */, 9AB7FD7C246B48DB00387FDA /* settings.swift in Sources */, 9AF9EE0F2464875F005D2270 /* main.swift in Sources */, ); @@ -1882,11 +1906,13 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application"; CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=macosx*]" = RP2S87B72W; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "$(SRCROOT)/SMC/Helper/Info.plist"; MACOSX_DEPLOYMENT_TARGET = 10.14; + MARKETING_VERSION = 1.0.1; "OTHER_LDFLAGS[arch=*]" = ( "-sectcreate", __TEXT, @@ -1912,11 +1938,13 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application"; CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=macosx*]" = RP2S87B72W; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "$(SRCROOT)/SMC/Helper/Info.plist"; MACOSX_DEPLOYMENT_TARGET = 10.14; + MARKETING_VERSION = 1.0.1; "OTHER_LDFLAGS[arch=*]" = ( "-sectcreate", __TEXT,