From bdb8c052cc43a5e6d20b4907da06d21153cbed6c Mon Sep 17 00:00:00 2001 From: Serhiy Mytrovtsiy Date: Sat, 15 Jun 2019 12:16:33 +0200 Subject: [PATCH] added new widget Chart with label; initialized widget protocol; --- Stats.xcodeproj/project.pbxproj | 28 +++--- Stats/MenuBar.swift | 6 +- Stats/Modules/CPU/CPU.swift | 80 +++++++++++++---- Stats/Modules/Disk/Disk.swift | 30 +++---- Stats/Modules/Disk/Disk.xib | 57 ------------ Stats/Modules/Memory/Memory.swift | 82 ++++++++++++----- Stats/Modules/Memory/Memory.xib | 57 ------------ .../CPU/CPUView.swift => Widgets/Chart.swift} | 88 ++++++++++++------- Stats/Widgets/Mini.swift | 76 ++++++++++++++++ Stats/libs/Module.swift | 69 ++++++++++++--- 10 files changed, 341 insertions(+), 232 deletions(-) delete mode 100644 Stats/Modules/Disk/Disk.xib delete mode 100644 Stats/Modules/Memory/Memory.xib rename Stats/{Modules/CPU/CPUView.swift => Widgets/Chart.swift} (61%) create mode 100644 Stats/Widgets/Mini.swift diff --git a/Stats.xcodeproj/project.pbxproj b/Stats.xcodeproj/project.pbxproj index 689f4841..412c0cc1 100755 --- a/Stats.xcodeproj/project.pbxproj +++ b/Stats.xcodeproj/project.pbxproj @@ -15,10 +15,9 @@ 9A5B1CBF229E78F0008B9D3C /* Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A5B1CBE229E78F0008B9D3C /* Observable.swift */; }; 9A5B1CC5229E7B40008B9D3C /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A5B1CC4229E7B40008B9D3C /* Extensions.swift */; }; 9A6CFC0122A1C9F5001E782D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9A6CFC0022A1C9F5001E782D /* Assets.xcassets */; }; - 9A74D59422B4315C004FE1FA /* CPUView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A74D59322B4315C004FE1FA /* CPUView.swift */; }; + 9A74D59422B4315C004FE1FA /* Chart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A74D59322B4315C004FE1FA /* Chart.swift */; }; + 9A74D59722B44498004FE1FA /* Mini.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A74D59622B44498004FE1FA /* Mini.swift */; }; 9A7B8F5E22A2A57600DEB352 /* CPUReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7B8F5D22A2A57600DEB352 /* CPUReader.swift */; }; - 9A7B8F6522A2C19D00DEB352 /* Memory.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9A7B8F6422A2C19D00DEB352 /* Memory.xib */; }; - 9A7B8F6722A2C1B900DEB352 /* Disk.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9A7B8F6622A2C1B900DEB352 /* Disk.xib */; }; 9A7B8F6922A2C3A100DEB352 /* Memory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7B8F6822A2C3A100DEB352 /* Memory.swift */; }; 9A7B8F6B22A2C3A700DEB352 /* Disk.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7B8F6A22A2C3A700DEB352 /* Disk.swift */; }; 9A7B8F6D22A2C3D600DEB352 /* MemoryReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7B8F6C22A2C3D600DEB352 /* MemoryReader.swift */; }; @@ -55,10 +54,9 @@ 9A5B1CBE229E78F0008B9D3C /* Observable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Observable.swift; sourceTree = ""; }; 9A5B1CC4229E7B40008B9D3C /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; 9A6CFC0022A1C9F5001E782D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 9A74D59322B4315C004FE1FA /* CPUView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPUView.swift; sourceTree = ""; }; + 9A74D59322B4315C004FE1FA /* Chart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Chart.swift; sourceTree = ""; }; + 9A74D59622B44498004FE1FA /* Mini.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mini.swift; sourceTree = ""; }; 9A7B8F5D22A2A57600DEB352 /* CPUReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPUReader.swift; sourceTree = ""; }; - 9A7B8F6422A2C19D00DEB352 /* Memory.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = Memory.xib; sourceTree = ""; }; - 9A7B8F6622A2C1B900DEB352 /* Disk.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = Disk.xib; sourceTree = ""; }; 9A7B8F6822A2C3A100DEB352 /* Memory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Memory.swift; sourceTree = ""; }; 9A7B8F6A22A2C3A700DEB352 /* Disk.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Disk.swift; sourceTree = ""; }; 9A7B8F6C22A2C3D600DEB352 /* MemoryReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryReader.swift; sourceTree = ""; }; @@ -113,6 +111,7 @@ 9A1410F7229E721100D29793 /* Stats */ = { isa = PBXGroup; children = ( + 9A74D59522B440D4004FE1FA /* Widgets */, 9A5B1CB3229E72A7008B9D3C /* Supporting Files */, 9A5B1CBA229E7892008B9D3C /* Modules */, 9A5B1CBD229E78D2008B9D3C /* libs */, @@ -153,11 +152,19 @@ path = libs; sourceTree = ""; }; + 9A74D59522B440D4004FE1FA /* Widgets */ = { + isa = PBXGroup; + children = ( + 9A74D59322B4315C004FE1FA /* Chart.swift */, + 9A74D59622B44498004FE1FA /* Mini.swift */, + ); + path = Widgets; + sourceTree = ""; + }; 9A7B8F5C22A2926500DEB352 /* CPU */ = { isa = PBXGroup; children = ( 9A57A19C22A1E3270033E318 /* CPU.swift */, - 9A74D59322B4315C004FE1FA /* CPUView.swift */, 9A7B8F5D22A2A57600DEB352 /* CPUReader.swift */, ); path = CPU; @@ -166,7 +173,6 @@ 9A7B8F6222A2C17000DEB352 /* Memory */ = { isa = PBXGroup; children = ( - 9A7B8F6422A2C19D00DEB352 /* Memory.xib */, 9A7B8F6822A2C3A100DEB352 /* Memory.swift */, 9A7B8F6C22A2C3D600DEB352 /* MemoryReader.swift */, ); @@ -176,7 +182,6 @@ 9A7B8F6322A2C17500DEB352 /* Disk */ = { isa = PBXGroup; children = ( - 9A7B8F6622A2C1B900DEB352 /* Disk.xib */, 9A7B8F6A22A2C3A700DEB352 /* Disk.swift */, 9A7B8F6E22A2C57000DEB352 /* DiskReader.swift */, ); @@ -285,8 +290,6 @@ buildActionMask = 2147483647; files = ( 9A6CFC0122A1C9F5001E782D /* Assets.xcassets in Resources */, - 9A7B8F6722A2C1B900DEB352 /* Disk.xib in Resources */, - 9A7B8F6522A2C19D00DEB352 /* Memory.xib in Resources */, 9A141100229E721200D29793 /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -310,7 +313,7 @@ 9A7B8F6F22A2C57000DEB352 /* DiskReader.swift in Sources */, 9A7B8F6922A2C3A100DEB352 /* Memory.swift in Sources */, 9A7B8F5E22A2A57600DEB352 /* CPUReader.swift in Sources */, - 9A74D59422B4315C004FE1FA /* CPUView.swift in Sources */, + 9A74D59422B4315C004FE1FA /* Chart.swift in Sources */, 9A7B8F6D22A2C3D600DEB352 /* MemoryReader.swift in Sources */, 9A57A18522A1D26D0033E318 /* MenuBar.swift in Sources */, 9A57A19D22A1E3270033E318 /* CPU.swift in Sources */, @@ -318,6 +321,7 @@ 9A5B1CBF229E78F0008B9D3C /* Observable.swift in Sources */, 9A7B8F6B22A2C3A700DEB352 /* Disk.swift in Sources */, 9A1410F9229E721100D29793 /* AppDelegate.swift in Sources */, + 9A74D59722B44498004FE1FA /* Mini.swift in Sources */, 9A5B1CC5229E7B40008B9D3C /* Extensions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Stats/MenuBar.swift b/Stats/MenuBar.swift index a367a5ab..bb70c190 100644 --- a/Stats/MenuBar.swift +++ b/Stats/MenuBar.swift @@ -10,7 +10,7 @@ import Cocoa import ServiceManagement let MODULE_HEIGHT = CGFloat(NSApplication.shared.mainMenu?.menuBarHeight ?? 22) -let MODULE_WIDTH = CGFloat(28) +let MODULE_WIDTH = CGFloat(32) class MenuBar { let defaults = UserDefaults.standard @@ -34,6 +34,8 @@ class MenuBar { for module in modules.value { module.active.subscribe(observer: self) { (value, _) in self.buildModulesView() + self.menuBarItem.menu?.removeAllItems() + self.menuBarItem.menu = self.buildMenu() } } } @@ -42,7 +44,7 @@ class MenuBar { let menu = NSMenu() for module in modules.value { - menu.addItem(module.menu()) + menu.addItem(module.menu) } menu.addItem(NSMenuItem.separator()) diff --git a/Stats/Modules/CPU/CPU.swift b/Stats/Modules/CPU/CPU.swift index a0ac3c68..a329235b 100644 --- a/Stats/Modules/CPU/CPU.swift +++ b/Stats/Modules/CPU/CPU.swift @@ -10,39 +10,51 @@ import Cocoa class CPU: Module { let name: String = "CPU" + let shortName: String = "CPU" var view: NSView = NSView() - let defaults = UserDefaults.standard - + var menu: NSMenuItem = NSMenuItem() + var submenu: NSMenu = NSMenu() var active: Observable var reader: Reader = CPUReader() + let defaults = UserDefaults.standard + var widgetType: WidgetType + init() { self.active = Observable(defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true) - self.view = ChartView(frame: NSMakeRect(0, 0, MODULE_WIDTH + 7, MODULE_HEIGHT)) + self.widgetType = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.Mini + initMenu() + initWidget() } - func start() { - if !self.reader.usage.value.isNaN { - (self.view as! ChartView).value(value: self.reader.usage!.value) - } + func initMenu() { + menu = NSMenuItem(title: name, action: #selector(toggle), keyEquivalent: "") + submenu = NSMenu() - self.reader.start() - self.reader.usage.subscribe(observer: self) { (value, _) in - if !value.isNaN { - (self.view as! ChartView).value(value: value) - } - } - } - - func menu() -> NSMenuItem { - let menu = NSMenuItem(title: name, action: #selector(toggle), keyEquivalent: "") if defaults.object(forKey: name) != nil { menu.state = defaults.bool(forKey: name) ? NSControl.StateValue.on : NSControl.StateValue.off } else { menu.state = NSControl.StateValue.on } menu.target = self - return menu + + let mini = NSMenuItem(title: "Mini", action: #selector(toggleWidget), keyEquivalent: "") + mini.state = self.widgetType == Widgets.Mini ? NSControl.StateValue.on : NSControl.StateValue.off + mini.target = self + + let chart = NSMenuItem(title: "Chart", action: #selector(toggleWidget), keyEquivalent: "") + chart.state = self.widgetType == Widgets.Chart ? NSControl.StateValue.on : NSControl.StateValue.off + chart.target = self + + let chartWithValue = NSMenuItem(title: "Chart with value", action: #selector(toggleWidget), keyEquivalent: "") + chartWithValue.state = self.widgetType == Widgets.ChartWithValue ? NSControl.StateValue.on : NSControl.StateValue.off + chartWithValue.target = self + + submenu.addItem(mini) + submenu.addItem(chart) + submenu.addItem(chartWithValue) + + menu.submenu = submenu } @objc func toggle(_ sender: NSMenuItem) { @@ -52,9 +64,41 @@ class CPU: Module { self.active << state if !state { + menu.submenu = nil self.stop() } else { + menu.submenu = submenu self.start() } } + + @objc func toggleWidget(_ sender: NSMenuItem) { + var widgetCode: Float = 0.0 + + switch sender.title { + case "Mini": + widgetCode = Widgets.Mini + case "Chart": + widgetCode = Widgets.Chart + case "Chart with value": + widgetCode = Widgets.ChartWithValue + default: + break + } + + if self.widgetType == widgetCode { + return + } + + for item in self.submenu.items { + if item.title == "Mini" || item.title == "Chart" || item.title == "Chart with value" { + item.state = NSControl.StateValue.off + } + } + + sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on + self.defaults.set(widgetCode, forKey: "\(name)_widget") + self.widgetType = widgetCode + self.initWidget() + } } diff --git a/Stats/Modules/Disk/Disk.swift b/Stats/Modules/Disk/Disk.swift index 708796dd..961dbcc7 100644 --- a/Stats/Modules/Disk/Disk.swift +++ b/Stats/Modules/Disk/Disk.swift @@ -10,9 +10,11 @@ import Cocoa class Disk: Module { let name: String = "Disk" - var colors: Bool = false + let shortName: String = "SSD" var view: NSView = NSView() + var menu: NSMenuItem = NSMenuItem() let defaults = UserDefaults.standard + var widgetType: WidgetType var active: Observable var reader: Reader = DiskReader() @@ -21,26 +23,17 @@ class Disk: Module { init() { self.active = Observable(defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true) - self.view = loadViewFromNib() - } - - func start() { - if !self.reader.usage.value.isNaN { - self.value.stringValue = "\(Int(Float(self.reader.usage.value.roundTo(decimalPlaces: 2))! * 100))%" - self.value.textColor = self.reader.usage.value.usageColor() - } + self.widgetType = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.Mini - self.reader.start() - self.reader.usage.subscribe(observer: self) { (value, _) in - if !value.isNaN { - self.value.stringValue = "\(Int(Float(value.roundTo(decimalPlaces: 2))! * 100))%" - self.value.textColor = value.usageColor() - } - } + self.initMenu() + + let widget = Mini(frame: NSMakeRect(0, 0, MODULE_WIDTH, MODULE_HEIGHT)) + widget.label = self.shortName + self.view = widget } - func menu() -> NSMenuItem { - let menu = NSMenuItem(title: name, action: #selector(toggle), keyEquivalent: "") + func initMenu() { + menu = NSMenuItem(title: name, action: #selector(toggle), keyEquivalent: "") if defaults.object(forKey: name) != nil { menu.state = defaults.bool(forKey: name) ? NSControl.StateValue.on : NSControl.StateValue.off } else { @@ -48,7 +41,6 @@ class Disk: Module { } menu.target = self menu.isEnabled = true - return menu } @objc func toggle(_ sender: NSMenuItem) { diff --git a/Stats/Modules/Disk/Disk.xib b/Stats/Modules/Disk/Disk.xib deleted file mode 100644 index d70326bb..00000000 --- a/Stats/Modules/Disk/Disk.xib +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Stats/Modules/Memory/Memory.swift b/Stats/Modules/Memory/Memory.swift index e5bb891d..95bc09bc 100644 --- a/Stats/Modules/Memory/Memory.swift +++ b/Stats/Modules/Memory/Memory.swift @@ -10,45 +10,53 @@ import Cocoa class Memory: Module { let name: String = "Memory" - var colors: Bool = false + let shortName: String = "MEM" var view: NSView = NSView() - let defaults = UserDefaults.standard - + var menu: NSMenuItem = NSMenuItem() + var submenu: NSMenu = NSMenu() var active: Observable var reader: Reader = MemoryReader() + var widgetType: WidgetType + + let defaults = UserDefaults.standard @IBOutlet weak var value: NSTextField! init() { self.active = Observable(defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true) - self.view = loadViewFromNib() + self.widgetType = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.Mini + initMenu() + initWidget() } - func start() { - if !self.reader.usage.value.isNaN { - self.value.stringValue = "\(Int(Float(self.reader.usage.value.roundTo(decimalPlaces: 2))! * 100))%" - self.value.textColor = self.reader.usage.value.usageColor() - } + func initMenu() { + menu = NSMenuItem(title: name, action: #selector(toggle), keyEquivalent: "") + submenu = NSMenu() - self.reader.start() - self.reader.usage.subscribe(observer: self) { (value, _) in - if !value.isNaN { - self.value.stringValue = "\(Int(Float(value.roundTo(decimalPlaces: 2))! * 100))%" - self.value.textColor = value.usageColor() - } - } - } - - func menu() -> NSMenuItem { - let menu = NSMenuItem(title: name, action: #selector(toggle), keyEquivalent: "") if defaults.object(forKey: name) != nil { menu.state = defaults.bool(forKey: name) ? NSControl.StateValue.on : NSControl.StateValue.off } else { menu.state = NSControl.StateValue.on } menu.target = self - menu.isEnabled = true - return menu + + let mini = NSMenuItem(title: "Mini", action: #selector(toggleWidget), keyEquivalent: "") + mini.state = self.widgetType == Widgets.Mini ? NSControl.StateValue.on : NSControl.StateValue.off + mini.target = self + + let chart = NSMenuItem(title: "Chart", action: #selector(toggleWidget), keyEquivalent: "") + chart.state = self.widgetType == Widgets.Chart ? NSControl.StateValue.on : NSControl.StateValue.off + chart.target = self + + let chartWithValue = NSMenuItem(title: "Chart with value", action: #selector(toggleWidget), keyEquivalent: "") + chartWithValue.state = self.widgetType == Widgets.ChartWithValue ? NSControl.StateValue.on : NSControl.StateValue.off + chartWithValue.target = self + + submenu.addItem(mini) + submenu.addItem(chart) + submenu.addItem(chartWithValue) + + menu.submenu = submenu } @objc func toggle(_ sender: NSMenuItem) { @@ -64,4 +72,34 @@ class Memory: Module { self.start() } } + + @objc func toggleWidget(_ sender: NSMenuItem) { + var widgetCode: Float = 0.0 + + switch sender.title { + case "Mini": + widgetCode = Widgets.Mini + case "Chart": + widgetCode = Widgets.Chart + case "Chart with value": + widgetCode = Widgets.ChartWithValue + default: + break + } + + if self.widgetType == widgetCode { + return + } + + for item in self.submenu.items { + if item.title == "Mini" || item.title == "Chart" || item.title == "Chart with value" { + item.state = NSControl.StateValue.off + } + } + + sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on + self.defaults.set(widgetCode, forKey: "\(name)_widget") + self.widgetType = widgetCode + self.initWidget() + } } diff --git a/Stats/Modules/Memory/Memory.xib b/Stats/Modules/Memory/Memory.xib deleted file mode 100644 index 3cf7dd3d..00000000 --- a/Stats/Modules/Memory/Memory.xib +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Stats/Modules/CPU/CPUView.swift b/Stats/Widgets/Chart.swift similarity index 61% rename from Stats/Modules/CPU/CPUView.swift rename to Stats/Widgets/Chart.swift index c6c12039..cdd76ab4 100644 --- a/Stats/Modules/CPU/CPUView.swift +++ b/Stats/Widgets/Chart.swift @@ -8,10 +8,8 @@ import Cocoa -class ChartView: NSView { - var valueLabel: NSTextField = NSTextField() - - var label: Bool = true +class Chart: NSView, Widget { + var height: CGFloat = 0.0 var points: [Float] { didSet { self.needsDisplay = true @@ -22,29 +20,8 @@ class ChartView: NSView { override init(frame: NSRect) { self.points = Array(repeating: 0.0, count: 50) super.init(frame: frame) - self.wantsLayer = true - - if self.label { - let valueLabel = NSTextField(frame: NSMakeRect(2, MODULE_HEIGHT - 11, self.frame.size.width, 10)) - valueLabel.textColor = NSColor.red - valueLabel.isEditable = false - valueLabel.isSelectable = false - valueLabel.isBezeled = false - valueLabel.wantsLayer = true - valueLabel.textColor = .labelColor - valueLabel.backgroundColor = .controlColor - valueLabel.canDrawSubviewsIntoLayer = true - valueLabel.alignment = .natural - valueLabel.font = NSFont.systemFont(ofSize: 8, weight: .ultraLight) - valueLabel.stringValue = "" - valueLabel.addSubview(NSView()) - - self.valueLabel = valueLabel - self.addSubview(self.valueLabel) - } else { - self.addSubview(NSView()) - } + self.addSubview(NSView()) } required init?(coder decoder: NSCoder) { @@ -60,9 +37,8 @@ class ChartView: NSView { let context = NSGraphicsContext.current!.cgContext let xOffset: CGFloat = 4.0 let yOffset: CGFloat = 3.0 - var height: CGFloat = self.frame.size.height - CGFloat((yOffset * 2)) - if self.label { - height = 7.0 + if height == 0 { + height = self.frame.size.height - CGFloat((yOffset * 2)) } let xRatio = Double(self.frame.size.width - (xOffset * 2)) / (Double(self.points.count) - 1) @@ -70,7 +46,7 @@ class ChartView: NSView { return CGFloat((Double(point) * xRatio)) + xOffset } let columnYPoint = { (point: Int) -> CGFloat in - return CGFloat((CGFloat(truncating: self.points[point] as NSNumber) * height)) + yOffset + return CGFloat((CGFloat(truncating: self.points[point] as NSNumber) * self.height)) + yOffset } let graphPath = NSBezierPath() @@ -99,16 +75,60 @@ class ChartView: NSView { context.restoreGState() - graphPath.lineWidth = 1.0 + graphPath.lineWidth = 0.5 graphPath.stroke() } func value(value: Float) { - if self.label { - self.valueLabel.stringValue = "\(Int(Float(Float(value).roundTo(decimalPlaces: 2))! * 100))%" - self.valueLabel.textColor = Float(value).usageColor() + if self.points.count < 50 { + self.points.append(value) + return } + for (i, _) in self.points.enumerated() { + if i+1 < self.points.count { + self.points[i] = self.points[i+1] + } else { + self.points[i] = value + } + } + } +} + +class ChartWithValue: Chart { + var valueLabel: NSTextField = NSTextField() + + override init(frame: NSRect) { + super.init(frame: frame) + + self.wantsLayer = true + + valueLabel = NSTextField(frame: NSMakeRect(2, MODULE_HEIGHT - 11, self.frame.size.width, 10)) + valueLabel.textColor = NSColor.red + valueLabel.isEditable = false + valueLabel.isSelectable = false + valueLabel.isBezeled = false + valueLabel.wantsLayer = true + valueLabel.textColor = .labelColor + valueLabel.backgroundColor = .controlColor + valueLabel.canDrawSubviewsIntoLayer = true + valueLabel.alignment = .natural + valueLabel.font = NSFont.systemFont(ofSize: 8, weight: .ultraLight) + valueLabel.stringValue = "" + valueLabel.addSubview(NSView()) + + self.height = 7.0 + self.addSubview(valueLabel) + } + + required init?(coder decoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func value(value: Float) { + self.valueLabel.stringValue = "\(Int(Float(Float(value).roundTo(decimalPlaces: 2))! * 100))%" + self.valueLabel.textColor = Float(value).usageColor() + if self.points.count < 50 { self.points.append(value) return diff --git a/Stats/Widgets/Mini.swift b/Stats/Widgets/Mini.swift new file mode 100644 index 00000000..dd0622df --- /dev/null +++ b/Stats/Widgets/Mini.swift @@ -0,0 +1,76 @@ +// +// Mini.swift +// Stats +// +// Created by Serhiy Mytrovtsiy on 14.06.2019. +// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa + +class Mini: NSView, Widget { + var valueView: NSTextField = NSTextField() + var labelView: NSTextField = NSTextField() + + var value: Float = 0 + var label: String = "" { + didSet { + self.labelView.stringValue = label + } + } + + override init(frame: NSRect) { + super.init(frame: frame) + + self.wantsLayer = true + + let xOffset: CGFloat = 1.0 + + let labelView = NSTextField(frame: NSMakeRect(xOffset, 13, self.frame.size.width, 7)) + labelView.textColor = NSColor.red + labelView.isEditable = false + labelView.isSelectable = false + labelView.isBezeled = false + labelView.wantsLayer = true + labelView.textColor = .labelColor + labelView.backgroundColor = .controlColor + labelView.canDrawSubviewsIntoLayer = true + labelView.alignment = .natural + labelView.font = NSFont.systemFont(ofSize: 7, weight: .ultraLight) + labelView.stringValue = self.label + labelView.addSubview(NSView()) + + let valueView = NSTextField(frame: NSMakeRect(xOffset, 3, self.frame.size.width, 10)) + valueView.textColor = NSColor.red + valueView.isEditable = false + valueView.isSelectable = false + valueView.isBezeled = false + valueView.wantsLayer = true + valueView.textColor = .labelColor + valueView.backgroundColor = .controlColor + valueView.canDrawSubviewsIntoLayer = true + valueView.alignment = .natural + valueView.font = NSFont.systemFont(ofSize: 10, weight: .regular) + valueView.stringValue = "" + valueView.addSubview(NSView()) + + self.labelView = labelView + self.valueView = valueView + + self.addSubview(self.labelView) + self.addSubview(self.valueView) + } + + required init?(coder decoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func value(value: Float) { + if self.value != value { + self.value = value + + self.valueView.stringValue = "\(Int(Float(Float(value).roundTo(decimalPlaces: 2))! * 100))%" + self.valueView.textColor = Float(value).usageColor() + } + } +} diff --git a/Stats/libs/Module.swift b/Stats/libs/Module.swift index a3d9160c..02839c38 100644 --- a/Stats/libs/Module.swift +++ b/Stats/libs/Module.swift @@ -8,30 +8,65 @@ import Cocoa -protocol Module { +protocol Module: class { var name: String { get } - var view: NSView { get } + var shortName: String { get } + var view: NSView { get set } + var menu: NSMenuItem { get } var active: Observable { get } var reader: Reader { get } + var widgetType: WidgetType { get } - func menu() -> NSMenuItem func start() func stop() } extension Module { + func initWidget() { + self.active << false + switch self.widgetType { + case Widgets.Mini: + let widget = Mini(frame: NSMakeRect(0, 0, MODULE_WIDTH, MODULE_HEIGHT)) + widget.label = self.shortName + self.view = widget + break + case Widgets.Chart: + self.view = Chart(frame: NSMakeRect(0, 0, MODULE_WIDTH + 7, MODULE_HEIGHT)) + break + case Widgets.ChartWithValue: + self.view = ChartWithValue(frame: NSMakeRect(0, 0, MODULE_WIDTH + 7, MODULE_HEIGHT)) + break + default: + let widget = Mini(frame: NSMakeRect(0, 0, MODULE_WIDTH, MODULE_HEIGHT)) + widget.label = self.shortName + self.view = widget + } + self.active << true + } + + func start() { + if !self.reader.usage.value.isNaN { + guard let widget = self.view as? Widget else { + return + } + widget.value(value: self.reader.usage.value) + } + + self.reader.start() + self.reader.usage.subscribe(observer: self as AnyObject) { (value, _) in + if !value.isNaN { + guard let widget = self.view as? Widget else { + return + } + widget.value(value: value) + } + } + } + func stop() { self.reader.stop() self.reader.usage.unsubscribe(observer: self as AnyObject) } - - func loadViewFromNib() -> NSView { - var topLevelObjects: NSArray? - if Bundle.main.loadNibNamed(NSNib.Name(String(describing: Self.self)), owner: self, topLevelObjects: &topLevelObjects) { - return (topLevelObjects?.first(where: { $0 is NSView } ) as? NSView)! - } - return NSView() - } } protocol Reader { @@ -40,3 +75,15 @@ protocol Reader { func read() func stop() } + +protocol Widget { + func value(value: Float) +} + +typealias WidgetType = Float + +struct Widgets { + static let Mini: WidgetType = 0.0 + static let Chart: WidgetType = 1.0 + static let ChartWithValue: WidgetType = 1.1 +}