new build and update algorithm for menu bar

This commit is contained in:
Serhiy Mytrovtsiy
2019-09-11 00:17:48 +02:00
parent e7235017fc
commit c08cecf147
21 changed files with 151 additions and 81 deletions

View File

@@ -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()
}
}

View File

@@ -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
}
}

View File

@@ -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() {

View File

@@ -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) {

View File

@@ -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

View File

@@ -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() {

View File

@@ -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) {

View File

@@ -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

View File

@@ -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)
}
}
}

View File

@@ -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)
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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))

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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<Bool> = 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() {}
}