From c08cecf1473aa0c5d501294fdc378475c76fb481 Mon Sep 17 00:00:00 2001 From: Serhiy Mytrovtsiy Date: Wed, 11 Sep 2019 00:17:48 +0200 Subject: [PATCH] new build and update algorithm for menu bar --- Stats/AppDelegate.swift | 9 +- Stats/MenuBar.swift | 101 ++++++++++++------ Stats/Modules/Battery/Battery.swift | 3 - Stats/Modules/CPU/CPU.swift | 9 +- Stats/Modules/CPU/CPUView.swift | 3 +- Stats/Modules/Disk/Disk.swift | 4 - Stats/Modules/Memory/Memory.swift | 3 - Stats/Modules/Memory/MemoryView.swift | 3 +- Stats/Modules/Module.swift | 17 ++- Stats/Modules/Network/Network.swift | 5 +- Stats/Widgets/BatteryWidget.swift | 4 + Stats/Widgets/Charts/BarChart.swift | 13 ++- Stats/Widgets/Charts/LineChart.swift | 7 +- Stats/Widgets/Charts/LineChartWithValue.swift | 7 +- Stats/Widgets/Mini.swift | 4 + Stats/Widgets/Network/NetworkArrows.swift | 4 + Stats/Widgets/Network/NetworkArrowsText.swift | 4 + Stats/Widgets/Network/NetworkDots.swift | 4 + Stats/Widgets/Network/NetworkDotsText.swift | 4 + Stats/Widgets/Network/NetworkText.swift | 4 + Stats/Widgets/Widget.swift | 20 ++++ 21 files changed, 151 insertions(+), 81 deletions(-) diff --git a/Stats/AppDelegate.swift b/Stats/AppDelegate.swift index 1eb47d14..370065d6 100755 --- a/Stats/AppDelegate.swift +++ b/Stats/AppDelegate.swift @@ -13,6 +13,7 @@ import LaunchAtLogin let modules: Observable<[Module]> = Observable([CPU(), Memory(), Disk(), Battery(), Network()]) let updater = macAppUpdater(user: "exelban", repo: "stats") let popover = NSPopover() +var menuBar: MenuBar? @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { @@ -27,9 +28,11 @@ class AppDelegate: NSObject, NSApplicationDelegate { menuBarButton.action = #selector(toggleMenu) popover.contentViewController = MainViewController.Init() - popover.behavior = NSPopover.Behavior.transient + popover.behavior = .transient + popover.animates = true - _ = MenuBar(menuBarItem, menuBarButton: menuBarButton) + menuBar = MenuBar(menuBarItem, menuBarButton: menuBarButton) + menuBar!.build() if self.defaults.object(forKey: "runAtLoginInitialized") == nil { LaunchAtLogin.isEnabled = true @@ -77,7 +80,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { } else { if let button = self.menuBarItem.button { NSApplication.shared.activate(ignoringOtherApps: true) - popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY) + popover.show(relativeTo: .zero, of: button, preferredEdge: .maxY) popover.becomeFirstResponder() } } diff --git a/Stats/MenuBar.swift b/Stats/MenuBar.swift index dc07c583..22983e72 100644 --- a/Stats/MenuBar.swift +++ b/Stats/MenuBar.swift @@ -10,71 +10,102 @@ import Cocoa import ServiceManagement class MenuBar { - private let defaults = UserDefaults.standard private let menuBarItem: NSStatusItem private var menuBarButton: NSButton = NSButton() - private var view: NSView? = nil + private var stackView: NSStackView = NSStackView() init(_ menuBarItem: NSStatusItem, menuBarButton: NSButton) { self.menuBarItem = menuBarItem self.menuBarButton = menuBarButton - generateMenuBar() - modules.subscribe(observer: self) { (_, _) in - self.generateMenuBar() - } - } - - private func generateMenuBar() { - buildModulesView() - for module in modules.value { module.active.subscribe(observer: self) { (value, _) in - self.buildModulesView() - self.menuBarItem.menu?.removeAllItems() - } - module.available.subscribe(observer: self) { (value, _) in - self.buildModulesView() - self.menuBarItem.menu?.removeAllItems() + if !value { + let emptyWidget = Empty() + emptyWidget.name = module.name + module.view = emptyWidget + } else { + module.initWidget() + } + module.initMenu(active: value) + module.initTab() + self.updateWidget(name: module.name) } } } - private func buildModulesView() { - if self.view == nil { - self.view = NSView(frame: NSMakeRect(0, 0, widgetSize.width, widgetSize.height)) - self.menuBarButton.addSubview(self.view!) + public func updateWidget(name: String) { + let newViewList = modules.value.filter{ $0.name == name } + if newViewList.isEmpty { + return + } + let oldViewList = self.stackView.subviews.filter{ ($0 as! Widget).name == name } + if oldViewList.isEmpty { + return } - let view = self.view! + let newView = newViewList.first!.view + let oldView = oldViewList.first! + + self.stackView.replaceSubview(oldView, with: newView) + self.updateWidth() + } + + private func updateWidth() { var WIDTH: CGFloat = 0 for module in modules.value { if module.active.value && module.available.value { - module.start() WIDTH = WIDTH + module.view.frame.size.width } } - self.menuBarButton.image = nil - for v in view.subviews { - v.removeFromSuperview() + if WIDTH == 0 { + self.menuBarButton.image = NSImage(named:NSImage.Name("tray_icon")) + self.menuBarItem.length = widgetSize.width + return } - var x: CGFloat = 0 + self.menuBarButton.image = nil + self.stackView.frame.size.width = WIDTH + self.menuBarItem.length = WIDTH + } + + public func build() { + let stackView = NSStackView(frame: NSRect(x: 0, y: 0, width: widgetSize.width, height: widgetSize.height)) + stackView.wantsLayer = true + stackView.orientation = .horizontal + stackView.distribution = .fillProportionally + stackView.spacing = 0 + self.stackView = stackView + + var WIDTH: CGFloat = 0 for module in modules.value { - if module.active.value && module.available.value { - module.view.frame = CGRect(x: x, y: 0, width: module.view.frame.size.width, height: module.view.frame.size.height) - view.addSubview(module.view) - x = x + module.view.frame.size.width + if module.available.value { + if module.active.value { + module.initWidget() + module.initTab() + module.start() + } else { + let emptyView = Empty() + emptyView.name = module.name + module.view = emptyView + } + module.initMenu(active: module.active.value) + stackView.addArrangedSubview(module.view) + WIDTH = WIDTH + module.view.frame.size.width } } - if view.subviews.count == 0 { + self.menuBarButton.addSubview(stackView) + + if WIDTH == 0 { self.menuBarButton.image = NSImage(named:NSImage.Name("tray_icon")) self.menuBarItem.length = widgetSize.width - } else { - self.menuBarItem.length = WIDTH - view.frame.size.width = WIDTH + return } + + self.menuBarButton.image = nil + self.stackView.frame.size.width = WIDTH + self.menuBarItem.length = WIDTH } } diff --git a/Stats/Modules/Battery/Battery.swift b/Stats/Modules/Battery/Battery.swift index 68b1691c..01415fe3 100644 --- a/Stats/Modules/Battery/Battery.swift +++ b/Stats/Modules/Battery/Battery.swift @@ -30,9 +30,6 @@ class Battery: Module { self.active = Observable(defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true) self.percentageView = Observable(defaults.object(forKey: "\(self.name)_percentage") != nil ? defaults.bool(forKey: "\(self.name)_percentage") : false) self.view = BatteryWidget(frame: NSMakeRect(0, 0, widgetSize.width, widgetSize.height)) - initMenu(active: self.active.value) - initWidget() - initTab() } func start() { diff --git a/Stats/Modules/CPU/CPU.swift b/Stats/Modules/CPU/CPU.swift index f3f35acb..33607247 100644 --- a/Stats/Modules/CPU/CPU.swift +++ b/Stats/Modules/CPU/CPU.swift @@ -25,6 +25,7 @@ class CPU: Module { private let defaults = UserDefaults.standard private var submenu: NSMenu = NSMenu() + private var initialized: Bool = false init() { self.available = Observable(true) @@ -35,12 +36,7 @@ class CPU: Module { if self.widgetType == Widgets.BarChart { (self.reader as! CPUReader).perCoreMode = true (self.reader as! CPUReader).hyperthreading = self.hyperthreading.value - self.reader.read() } - - initWidget() - initMenu(active: self.active.value) - initTab() } func initMenu(active: Bool) { @@ -146,10 +142,9 @@ class CPU: Module { sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on self.defaults.set(widgetCode, forKey: "\(name)_widget") self.widgetType = widgetCode - self.active << false self.initWidget() self.initMenu(active: true) - self.active << true + menuBar!.updateWidget(name: self.name) } @objc func toggleHyperthreading(_ sender: NSMenuItem) { diff --git a/Stats/Modules/CPU/CPUView.swift b/Stats/Modules/CPU/CPUView.swift index b6c03e77..c41c36a3 100644 --- a/Stats/Modules/CPU/CPUView.swift +++ b/Stats/Modules/CPU/CPUView.swift @@ -63,7 +63,8 @@ extension CPU { marker.chartView = self.chart self.chart.marker = marker - let lineChartEntry = [ChartDataEntry]() + var lineChartEntry = [ChartDataEntry]() + lineChartEntry.append(ChartDataEntry(x: 0, y: 0)) let chartDataSet = LineChartDataSet(entries: lineChartEntry, label: "\(self.name) Usage") chartDataSet.drawCirclesEnabled = false chartDataSet.mode = .cubicBezier diff --git a/Stats/Modules/Disk/Disk.swift b/Stats/Modules/Disk/Disk.swift index 05e6bb23..115b05d3 100644 --- a/Stats/Modules/Disk/Disk.swift +++ b/Stats/Modules/Disk/Disk.swift @@ -29,10 +29,6 @@ class Disk: Module { self.available = Observable(true) self.active = Observable(defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true) self.widgetType = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.Mini - - self.initWidget() - self.initMenu(active: self.active.value) - initTab() } func initTab() { diff --git a/Stats/Modules/Memory/Memory.swift b/Stats/Modules/Memory/Memory.swift index e8657ec0..c825db14 100644 --- a/Stats/Modules/Memory/Memory.swift +++ b/Stats/Modules/Memory/Memory.swift @@ -29,9 +29,6 @@ class Memory: Module { self.available = Observable(true) self.active = Observable(defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true) self.widgetType = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.Mini - initWidget() - initTab() - initMenu(active: self.active.value) } func initMenu(active: Bool) { diff --git a/Stats/Modules/Memory/MemoryView.swift b/Stats/Modules/Memory/MemoryView.swift index 4e2ba8c9..0fe705bf 100644 --- a/Stats/Modules/Memory/MemoryView.swift +++ b/Stats/Modules/Memory/MemoryView.swift @@ -63,7 +63,8 @@ extension Memory { marker.chartView = self.chart self.chart.marker = marker - let lineChartEntry = [ChartDataEntry]() + var lineChartEntry = [ChartDataEntry]() + lineChartEntry.append(ChartDataEntry(x: 0, y: 0)) let chartDataSet = LineChartDataSet(entries: lineChartEntry, label: "\(self.name) Usage") chartDataSet.drawCirclesEnabled = false chartDataSet.mode = .cubicBezier diff --git a/Stats/Modules/Module.swift b/Stats/Modules/Module.swift index 11c0bb21..ad1ae3b9 100644 --- a/Stats/Modules/Module.swift +++ b/Stats/Modules/Module.swift @@ -26,9 +26,13 @@ protocol Module: class { func start() func stop() + + func initMenu(active: Bool) + func initTab() } extension Module { + func initWidget(label: Bool = false) { var widget: Widget = Mini() @@ -64,20 +68,15 @@ extension Module { } func start() { + self.reader.start() + if !self.reader.value.value.isEmpty { - guard let widget = self.view as? Widget else { - return - } - widget.setValue(data: self.reader.value.value) + (self.view as! Widget).setValue(data: self.reader.value.value) } - self.reader.start() self.reader.value.subscribe(observer: self) { (value, _) in if !value.isEmpty { - guard let widget = self.view as? Widget else { - return - } - widget.setValue(data: value) + (self.view as! Widget).setValue(data: value) } } } diff --git a/Stats/Modules/Network/Network.swift b/Stats/Modules/Network/Network.swift index 9ccd5444..cce66623 100644 --- a/Stats/Modules/Network/Network.swift +++ b/Stats/Modules/Network/Network.swift @@ -27,9 +27,6 @@ class Network: Module { self.available = Observable(self.reader.available) self.active = Observable(defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true) self.widgetType = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.NetworkDots - initMenu(active: self.active.value) - initWidget() - initTab() } func initTab() { @@ -54,7 +51,7 @@ class Network: Module { self.reader.start() self.reader.value.subscribe(observer: self) { (value, _) in - if !value.isEmpty { + if !value.isEmpty { (self.view as! Widget).setValue(data: value) } } diff --git a/Stats/Widgets/BatteryWidget.swift b/Stats/Widgets/BatteryWidget.swift index 2c483c68..04af61f9 100644 --- a/Stats/Widgets/BatteryWidget.swift +++ b/Stats/Widgets/BatteryWidget.swift @@ -39,6 +39,10 @@ class BatteryWidget: NSView, Widget { var percentageValue: NSTextField = NSTextField() + override var intrinsicContentSize: CGSize { + return CGSize(width: self.frame.size.width, height: self.frame.size.height) + } + override init(frame: NSRect) { self.value = 0.0 self.charging = false diff --git a/Stats/Widgets/Charts/BarChart.swift b/Stats/Widgets/Charts/BarChart.swift index 8d025294..cf381805 100644 --- a/Stats/Widgets/Charts/BarChart.swift +++ b/Stats/Widgets/Charts/BarChart.swift @@ -26,10 +26,14 @@ class BarChart: NSView, Widget { } } + override var intrinsicContentSize: CGSize { + return CGSize(width: self.frame.size.width, height: self.frame.size.height) + } + override init(frame: NSRect) { self.label = defaults.object(forKey: "\(name)_label") != nil ? defaults.bool(forKey: "\(name)_label") : true self.partitions = Array(repeating: 0.0, count: 1) - super.init(frame: CGRect(x: 0, y: 0, width: self.size, height: widgetSize.height)) + super.init(frame: CGRect(x: 0, y: 0, width: 0, height: widgetSize.height)) self.wantsLayer = true self.addSubview(NSView()) } @@ -41,10 +45,6 @@ class BarChart: NSView, Widget { func Init() { self.label = defaults.object(forKey: "\(name)_label") != nil ? defaults.bool(forKey: "\(name)_label") : true self.initPreferences() - - if self.label { - self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: self.frame.size.width + labelPadding, height: self.frame.size.height) - } } func initPreferences() { @@ -136,9 +136,8 @@ class BarChart: NSView, Widget { } if self.frame.size.width != width { - self.activeModule << false self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: width, height: self.frame.size.height) - self.activeModule << true + menuBar!.updateWidget(name: self.name) } self.needsDisplay = true diff --git a/Stats/Widgets/Charts/LineChart.swift b/Stats/Widgets/Charts/LineChart.swift index ff791ce6..80518342 100644 --- a/Stats/Widgets/Charts/LineChart.swift +++ b/Stats/Widgets/Charts/LineChart.swift @@ -25,6 +25,10 @@ class Chart: NSView, Widget { } } + override var intrinsicContentSize: CGSize { + return CGSize(width: self.frame.size.width, height: self.frame.size.height) + } + override init(frame: NSRect) { self.points = Array(repeating: 0.0, count: 50) super.init(frame: CGRect(x: 0, y: 0, width: self.size, height: widgetSize.height)) @@ -63,10 +67,9 @@ class Chart: NSView, Widget { width = width + labelPadding } - self.activeModule << false self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: width, height: self.frame.size.height) - self.activeModule << true self.redraw() + menuBar!.updateWidget(name: self.name) } override func draw(_ dirtyRect: NSRect) { diff --git a/Stats/Widgets/Charts/LineChartWithValue.swift b/Stats/Widgets/Charts/LineChartWithValue.swift index 5e2dd38b..d72a13bd 100644 --- a/Stats/Widgets/Charts/LineChartWithValue.swift +++ b/Stats/Widgets/Charts/LineChartWithValue.swift @@ -12,6 +12,10 @@ class ChartWithValue: Chart { var valueLabel: NSTextField = NSTextField() var color: Bool = false + override var intrinsicContentSize: CGSize { + return CGSize(width: self.frame.size.width, height: self.frame.size.height) + } + override init(frame: NSRect) { super.init(frame: CGRect(x: 0, y: 0, width: widgetSize.width + 7, height: widgetSize.height)) self.wantsLayer = true @@ -100,10 +104,9 @@ class ChartWithValue: Chart { if self.label { width = width + labelPadding } - self.activeModule << false self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: width, height: self.frame.size.height) - self.activeModule << true self.drawValue() + menuBar!.updateWidget(name: self.name) } @objc func toggleColor(_ sender: NSMenuItem) { diff --git a/Stats/Widgets/Mini.swift b/Stats/Widgets/Mini.swift index 56fb21e1..4ccd47b2 100644 --- a/Stats/Widgets/Mini.swift +++ b/Stats/Widgets/Mini.swift @@ -26,6 +26,10 @@ class Mini: NSView, Widget { } } + override var intrinsicContentSize: CGSize { + return CGSize(width: self.frame.size.width, height: self.frame.size.height) + } + override init(frame: NSRect) { super.init(frame: CGRect(x: 0, y: 0, width: self.size, height: widgetSize.height)) diff --git a/Stats/Widgets/Network/NetworkArrows.swift b/Stats/Widgets/Network/NetworkArrows.swift index 5905f8ed..0e0fa7c2 100644 --- a/Stats/Widgets/Network/NetworkArrows.swift +++ b/Stats/Widgets/Network/NetworkArrows.swift @@ -27,6 +27,10 @@ class NetworkArrowsView: NSView, Widget { } } + override var intrinsicContentSize: CGSize { + return CGSize(width: self.frame.size.width, height: self.frame.size.height) + } + override init(frame: NSRect) { self.download = 0 self.upload = 0 diff --git a/Stats/Widgets/Network/NetworkArrowsText.swift b/Stats/Widgets/Network/NetworkArrowsText.swift index 4b250f4b..56755c61 100644 --- a/Stats/Widgets/Network/NetworkArrowsText.swift +++ b/Stats/Widgets/Network/NetworkArrowsText.swift @@ -30,6 +30,10 @@ class NetworkArrowsTextView: NSView, Widget { var downloadValue: NSTextField = NSTextField() var uploadValue: NSTextField = NSTextField() + override var intrinsicContentSize: CGSize { + return CGSize(width: self.frame.size.width, height: self.frame.size.height) + } + override init(frame: NSRect) { self.download = 0 self.upload = 0 diff --git a/Stats/Widgets/Network/NetworkDots.swift b/Stats/Widgets/Network/NetworkDots.swift index 4b8c2652..56dd3927 100644 --- a/Stats/Widgets/Network/NetworkDots.swift +++ b/Stats/Widgets/Network/NetworkDots.swift @@ -27,6 +27,10 @@ class NetworkDotsView: NSView, Widget { } } + override var intrinsicContentSize: CGSize { + return CGSize(width: self.frame.size.width, height: self.frame.size.height) + } + override init(frame: NSRect) { self.download = 0 self.upload = 0 diff --git a/Stats/Widgets/Network/NetworkDotsText.swift b/Stats/Widgets/Network/NetworkDotsText.swift index 3a62c75f..a994a71c 100644 --- a/Stats/Widgets/Network/NetworkDotsText.swift +++ b/Stats/Widgets/Network/NetworkDotsText.swift @@ -27,6 +27,10 @@ class NetworkDotsTextView: NSView, Widget { } } + override var intrinsicContentSize: CGSize { + return CGSize(width: self.frame.size.width, height: self.frame.size.height) + } + var downloadValue: NSTextField = NSTextField() var uploadValue: NSTextField = NSTextField() diff --git a/Stats/Widgets/Network/NetworkText.swift b/Stats/Widgets/Network/NetworkText.swift index f7b0466f..c2e3cdff 100644 --- a/Stats/Widgets/Network/NetworkText.swift +++ b/Stats/Widgets/Network/NetworkText.swift @@ -19,6 +19,10 @@ class NetworkTextView: NSView, Widget { var downloadValue: NSTextField = NSTextField() var uploadValue: NSTextField = NSTextField() + override var intrinsicContentSize: CGSize { + return CGSize(width: self.frame.size.width, height: self.frame.size.height) + } + override init(frame: NSRect) { super.init(frame: CGRect(x: 0, y: 0, width: self.size, height: widgetSize.height)) self.wantsLayer = true diff --git a/Stats/Widgets/Widget.swift b/Stats/Widgets/Widget.swift index 30cdf6e3..6dd6a34d 100644 --- a/Stats/Widgets/Widget.swift +++ b/Stats/Widgets/Widget.swift @@ -42,3 +42,23 @@ struct WidgetSize { let margin: CGFloat = 2 } let widgetSize = WidgetSize() + + +class Empty: NSView, Widget { + var name: String = "Empty" + var shortName: String = "empty" + var activeModule: Observable = Observable(false) + var menus: [NSMenuItem] = [] + + override init(frame: NSRect) { + super.init(frame: CGRect(x: 0, y: 0, width: 0, height: widgetSize.height)) + } + + required init?(coder decoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setValue(data: [Double]) {} + func redraw() {} + func Init() {} +}