mirror of
https://github.com/morgan9e/macos-stats
synced 2026-04-15 00:34:08 +09:00
new build and update algorithm for menu bar
This commit is contained in:
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user