From 8b9698006f4e0968f7f0cc34a769d034b7073efa Mon Sep 17 00:00:00 2001 From: Serhiy Mytrovtsiy Date: Wed, 8 Jul 2020 17:50:40 +0200 Subject: [PATCH] - add an option to select a color for each module (from a predefined list) - add a separator to select color menu --- ModuleKit/Widgets/BarChart.swift | 101 +++++++++++------------------- ModuleKit/Widgets/LineChart.swift | 98 ++++++++++++++++------------- ModuleKit/Widgets/Mini.swift | 62 ++++++++++++------ ModuleKit/Widgets/Sensors.swift | 1 + ModuleKit/widget.swift | 40 ++++++++++++ Modules/CPU/config.plist | 12 ++++ Modules/Disk/config.plist | 10 ++- Modules/Disk/popup.swift | 4 +- Modules/Memory/main.swift | 1 + StatsKit/extensions.swift | 99 +++++++++++++++++++++++++++++ 10 files changed, 299 insertions(+), 129 deletions(-) diff --git a/ModuleKit/Widgets/BarChart.swift b/ModuleKit/Widgets/BarChart.swift index 56963813..83bffc5a 100644 --- a/ModuleKit/Widgets/BarChart.swift +++ b/ModuleKit/Widgets/BarChart.swift @@ -15,10 +15,10 @@ import StatsKit public class BarChart: Widget { private var labelState: Bool = true private var boxState: Bool = true - private var colorState: Bool = false - private var pressureState: Bool = false + private var colorState: widget_c = .systemAccent private let store: UnsafePointer? + private var colors: [widget_c] = widget_c.allCases private var value: [Double] = [] private var pressureLevel: Int = 0 @@ -49,8 +49,15 @@ public class BarChart: Widget { if let box = configuration["Box"] as? Bool { self.boxState = box } - if let color = configuration["Color"] as? Bool { - self.colorState = color + if let colorsToDisable = configuration["Unsupported colors"] as? [String] { + self.colors = self.colors.filter { (color: widget_c) -> Bool in + return !colorsToDisable.contains("\(color.self)") + } + } + if let color = configuration["Color"] as? String { + if let defaultColor = colors.first(where: { "\($0.self)" == color }) { + self.colorState = defaultColor + } } } super.init(frame: CGRect(x: 0, y: Constants.Widget.margin, width: Constants.Widget.width, height: Constants.Widget.height - (2*Constants.Widget.margin))) @@ -62,8 +69,7 @@ public class BarChart: Widget { if self.store != nil && !preview { self.boxState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_box", defaultValue: self.boxState) self.labelState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_label", defaultValue: self.labelState) - self.colorState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_color", defaultValue: self.colorState) - self.pressureState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_pressure", defaultValue: self.pressureState) + self.colorState = widget_c(rawValue: store!.pointee.string(key: "\(self.title)_\(self.type.rawValue)_color", defaultValue: self.colorState.rawValue)) ?? self.colorState } if preview { @@ -72,7 +78,6 @@ public class BarChart: Widget { } self.setFrameSize(NSSize(width: 36, height: self.frame.size.height)) self.invalidateIntrinsicContentSize() - self.pressureState = false } } @@ -146,6 +151,7 @@ public class BarChart: Widget { box.stroke() box.fill() chartPadding = 1 + x += 0.5 } let widthForBarChart = box.bounds.width - chartPadding @@ -154,19 +160,24 @@ public class BarChart: Widget { let partitionWidth: CGFloat = (widthForBarChart / CGFloat(self.value.count)) - CGFloat(partitionsMargin.isNaN ? 0 : partitionsMargin) let maxPartitionHeight: CGFloat = box.bounds.height - (chartPadding*2) - x += partitionMargin for i in 0..? private var chart: LineChartView + private var colors: [widget_c] = widget_c.allCases private var value: Double = 0 private var pressureLevel: Int = 0 @@ -40,8 +41,15 @@ public class LineChart: Widget { if let value = config!["Value"] as? Bool { self.valueState = value } - if let color = config!["Color"] as? Bool { - self.colorState = color + if let colorsToDisable = config!["Unsupported colors"] as? [String] { + self.colors = self.colors.filter { (color: widget_c) -> Bool in + return !colorsToDisable.contains("\(color.self)") + } + } + if let color = config!["Color"] as? String { + if let defaultColor = colors.first(where: { "\($0.self)" == color }) { + self.colorState = defaultColor + } } } self.chart = LineChartView(frame: NSRect(x: 0, y: 0, width: Constants.Widget.width, height: Constants.Widget.height - (2*Constants.Widget.margin)), num: 60) @@ -55,8 +63,8 @@ public class LineChart: Widget { self.boxState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_box", defaultValue: self.boxState) self.valueState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_value", defaultValue: self.valueState) self.labelState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_label", defaultValue: self.labelState) - self.colorState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_color", defaultValue: self.colorState) - self.pressureState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_pressure", defaultValue: self.pressureState) + self.valueColorState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_valueColor", defaultValue: self.valueColorState) + self.colorState = widget_c(rawValue: store!.pointee.string(key: "\(self.title)_\(self.type.rawValue)_color", defaultValue: self.colorState.rawValue)) ?? self.colorState } if self.labelState { @@ -70,7 +78,6 @@ public class LineChart: Widget { } self.chart.points = list self.value = 0.38 - self.pressureState = false } } @@ -111,18 +118,32 @@ public class LineChart: Widget { var boxRadius: CGFloat = 2 let boxWidth: CGFloat = Constants.Widget.width - (Constants.Widget.margin*2) + var color: NSColor = NSColor.controlAccentColor + switch self.colorState { + case .systemAccent: color = NSColor.controlAccentColor + case .utilization: color = value.usageColor() + case .pressure: color = self.pressureLevel.pressureColor() + case .monochrome: + if self.boxState { + color = (isDarkMode ? NSColor.black : NSColor.white) + } else { + color = (isDarkMode ? NSColor.white : NSColor.black) + } + default: color = colorFromString("\(self.colorState.self)") + } + if self.valueState { let style = NSMutableParagraphStyle() style.alignment = .right - var color = isDarkMode ? NSColor.white : NSColor.black - if self.colorState { - color = self.value.percentageColor(color: self.colorState) + var valueColor = isDarkMode ? NSColor.white : NSColor.black + if self.valueColorState { + valueColor = color } let stringAttributes = [ NSAttributedString.Key.font: NSFont.systemFont(ofSize: 8, weight: .regular), - NSAttributedString.Key.foregroundColor: color, + NSAttributedString.Key.foregroundColor: valueColor, NSAttributedString.Key.paragraphStyle: style ] @@ -146,11 +167,7 @@ public class LineChart: Widget { } chart.setFrameSize(NSSize(width: box.bounds.width - chartPadding, height: box.bounds.height - (chartPadding*2))) - if self.pressureState { - self.chart.color = self.pressureLevel.pressureColor() - } else { - self.chart.color = NSColor.controlAccentColor - } + self.chart.color = color chart.draw(NSRect(x: box.bounds.origin.x + 1, y: chartPadding, width: chart.frame.width, height: chart.frame.height)) ctx.restoreGState() @@ -163,47 +180,46 @@ public class LineChart: Widget { public override func settings(superview: NSView) { let rowHeight: CGFloat = 30 - let settingsNumber: CGFloat = self.title == "RAM" ? 5 : 4 + let settingsNumber: CGFloat = 5 let height: CGFloat = ((rowHeight + Constants.Settings.margin) * settingsNumber) + Constants.Settings.margin superview.setFrameSize(NSSize(width: superview.frame.width, height: height)) let view: NSView = NSView(frame: NSRect(x: Constants.Settings.margin, y: Constants.Settings.margin, width: superview.frame.width - (Constants.Settings.margin*2), height: superview.frame.height - (Constants.Settings.margin*2))) - if self.title == "RAM" { - view.addSubview(ToggleTitleRow( - frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 4, width: view.frame.width, height: rowHeight), - title: "Pressure level", - action: #selector(togglePressure), - state: self.pressureState - )) - } - view.addSubview(ToggleTitleRow( - frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 3, width: view.frame.width, height: rowHeight), + frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 4, width: view.frame.width, height: rowHeight), title: "Label", action: #selector(toggleLabel), state: self.labelState )) view.addSubview(ToggleTitleRow( - frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 2, width: view.frame.width, height: rowHeight), + frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 3, width: view.frame.width, height: rowHeight), title: "Box", action: #selector(toggleBox), state: self.boxState )) view.addSubview(ToggleTitleRow( - frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 1, width: view.frame.width, height: rowHeight), + frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 2, width: view.frame.width, height: rowHeight), title: "Value", action: #selector(toggleValue), state: self.valueState )) + view.addSubview(SelectColorRow( + frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 1, width: view.frame.width, height: rowHeight), + title: "Color", + action: #selector(toggleColor), + items: self.colors.map{ $0.rawValue }, + selected: self.colorState.rawValue + )) + view.addSubview(ToggleTitleRow( frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 0, width: view.frame.width, height: rowHeight), title: "Colorize value", - action: #selector(toggleColor), - state: self.colorState + action: #selector(toggleValueColor), + state: self.valueColorState )) superview.addSubview(view) @@ -271,27 +287,23 @@ public class LineChart: Widget { self.display() } - @objc private func toggleColor(_ sender: NSControl) { - var state: NSControl.StateValue? = nil - if #available(OSX 10.15, *) { - state = sender is NSSwitch ? (sender as! NSSwitch).state: nil - } else { - state = sender is NSButton ? (sender as! NSButton).state: nil + @objc private func toggleColor(_ sender: NSMenuItem) { + if let newColor = widget_c.allCases.first(where: { $0.rawValue == sender.title }) { + self.colorState = newColor + self.store?.pointee.set(key: "\(self.title)_\(self.type.rawValue)_color", value: self.colorState.rawValue) + self.display() } - self.colorState = state! == .on ? true : false - self.store?.pointee.set(key: "\(self.title)_\(self.type.rawValue)_color", value: self.colorState) - self.display() } - @objc private func togglePressure(_ sender: NSControl) { + @objc private func toggleValueColor(_ sender: NSControl) { var state: NSControl.StateValue? = nil if #available(OSX 10.15, *) { state = sender is NSSwitch ? (sender as! NSSwitch).state: nil } else { state = sender is NSButton ? (sender as! NSButton).state: nil } - self.pressureState = state! == .on ? true : false - self.store?.pointee.set(key: "\(self.title)_\(self.type.rawValue)_pressure", value: self.pressureState) + self.valueColorState = state! == .on ? true : false + self.store?.pointee.set(key: "\(self.title)_\(self.type.rawValue)_valueColor", value: self.valueColorState) self.display() } } diff --git a/ModuleKit/Widgets/Mini.swift b/ModuleKit/Widgets/Mini.swift index 81901f7a..6e17ed41 100644 --- a/ModuleKit/Widgets/Mini.swift +++ b/ModuleKit/Widgets/Mini.swift @@ -13,12 +13,14 @@ import Cocoa import StatsKit public class Mini: Widget { - public var colorState: Bool = false public var labelState: Bool = true + private var colorState: widget_c = .monochrome private let onlyValueWidth: CGFloat = 38 private var value: Double = 0 private let store: UnsafePointer? + private var colors: [widget_c] = widget_c.allCases + private var pressureLevel: Int = 0 public init(preview: Bool, title: String, config: NSDictionary?, store: UnsafePointer?) { var widgetTitle: String = title @@ -45,8 +47,15 @@ public class Mini: Widget { if let label = configuration["Label"] as? Bool { self.labelState = label } - if let color = configuration["Color"] as? Bool { - self.colorState = color + if let colorsToDisable = configuration["Unsupported colors"] as? [String] { + self.colors = self.colors.filter { (color: widget_c) -> Bool in + return !colorsToDisable.contains("\(color.self)") + } + } + if let color = configuration["Color"] as? String { + if let defaultColor = colors.first(where: { "\($0.self)" == color }) { + self.colorState = defaultColor + } } } super.init(frame: CGRect(x: 0, y: Constants.Widget.margin, width: Constants.Widget.width, height: Constants.Widget.height - (2*Constants.Widget.margin))) @@ -56,7 +65,7 @@ public class Mini: Widget { self.canDrawConcurrently = true if self.store != nil { - self.colorState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_color", defaultValue: self.colorState) + self.colorState = widget_c(rawValue: store!.pointee.string(key: "\(self.title)_\(self.type.rawValue)_color", defaultValue: self.colorState.rawValue)) ?? self.colorState self.labelState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_label", defaultValue: self.labelState) } } @@ -87,9 +96,18 @@ public class Mini: Widget { style.alignment = .left } + var color: NSColor = NSColor.controlAccentColor + switch self.colorState { + case .systemAccent: color = NSColor.controlAccentColor + case .utilization: color = value.usageColor() + case .pressure: color = self.pressureLevel.pressureColor() + case .monochrome: color = (isDarkMode ? NSColor.white : NSColor.black) + default: color = colorFromString("\(self.colorState.self)") + } + let stringAttributes = [ NSAttributedString.Key.font: NSFont.systemFont(ofSize: valueSize, weight: .regular), - NSAttributedString.Key.foregroundColor: self.value.percentageColor(color: self.colorState), + NSAttributedString.Key.foregroundColor: color, NSAttributedString.Key.paragraphStyle: style ] let rect = CGRect(x: x, y: y, width: width - (Constants.Widget.margin*2), height: valueSize) @@ -117,26 +135,23 @@ public class Mini: Widget { state: self.labelState )) - view.addSubview(ToggleTitleRow( - frame: NSRect(x: 0, y: 0, width: view.frame.width, height: rowHeight), - title: "Colorize", + view.addSubview(SelectColorRow( + frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 0, width: view.frame.width, height: rowHeight), + title: "Color", action: #selector(toggleColor), - state: self.colorState + items: self.colors.map{ $0.rawValue }, + selected: self.colorState.rawValue )) superview.addSubview(view) } - @objc private func toggleColor(_ sender: NSControl) { - var state: NSControl.StateValue? = nil - if #available(OSX 10.15, *) { - state = sender is NSSwitch ? (sender as! NSSwitch).state: nil - } else { - state = sender is NSButton ? (sender as! NSButton).state: nil + @objc private func toggleColor(_ sender: NSMenuItem) { + if let newColor = widget_c.allCases.first(where: { $0.rawValue == sender.title }) { + self.colorState = newColor + self.store?.pointee.set(key: "\(self.title)_\(self.type.rawValue)_color", value: self.colorState.rawValue) + self.display() } - self.colorState = state! == .on ? true : false - self.store?.pointee.set(key: "\(self.title)_\(self.type.rawValue)_color", value: self.colorState) - self.display() } @objc private func toggleLabel(_ sender: NSControl) { @@ -161,4 +176,15 @@ public class Mini: Widget { self.display() }) } + + public func setPressure(_ level: Int) { + guard self.pressureLevel != level else { + return + } + + self.pressureLevel = level + DispatchQueue.main.async(execute: { + self.display() + }) + } } diff --git a/ModuleKit/Widgets/Sensors.swift b/ModuleKit/Widgets/Sensors.swift index fdee0cb3..54b812e8 100644 --- a/ModuleKit/Widgets/Sensors.swift +++ b/ModuleKit/Widgets/Sensors.swift @@ -41,6 +41,7 @@ public class SensorsWidget: Widget { self.type = .sensors self.preview = preview self.canDrawConcurrently = true + self.layer?.backgroundColor = NSColor.clear.cgColor if self.store != nil { self.labelState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_label", defaultValue: self.labelState) diff --git a/ModuleKit/widget.swift b/ModuleKit/widget.swift index b2f14c22..545f1166 100644 --- a/ModuleKit/widget.swift +++ b/ModuleKit/widget.swift @@ -12,6 +12,46 @@ import Cocoa import StatsKit +public enum widget_c: String { + case utilization = "Based on utilization" + case pressure = "Based on pressure" + + case separator_1 = "separator_1" + + case systemAccent = "System accent" + case monochrome = "Monochrome accent" + + case separator_2 = "separator_2" + + case clear = "Clear" + case white = "White" + case black = "Black" + case gray = "Gray" + case secondGray = "Second gray" + case darkGray = "Dark gray" + case lightGray = "Light gray" + case red = "Red" + case secondRed = "Second red" + case green = "Green" + case secondGreen = "Second green" + case blue = "Blue" + case secondBlue = "Second blue" + case yellow = "Yellow" + case secondYellow = "Second yellow" + case orange = "Orange" + case secondOrange = "Second orange" + case purple = "Purple" + case secondPurple = "Second purple" + case brown = "Brown" + case secondBrown = "Second brown" + case cyan = "Cyan" + case magenta = "Magenta" + case pink = "Pink" + case teal = "Teal" + case indigo = "Indigo" +} +extension widget_c: CaseIterable {} + public enum widget_t: String { case unknown = "" case mini = "mini" diff --git a/Modules/CPU/config.plist b/Modules/CPU/config.plist index 5da4ed48..718537be 100644 --- a/Modules/CPU/config.plist +++ b/Modules/CPU/config.plist @@ -17,11 +17,19 @@ Value 0.08 + Unsupported colors + + pressure + line_chart Default + Unsupported colors + + pressure + bar_chart @@ -34,6 +42,10 @@ Color + Unsupported colors + + pressure + diff --git a/Modules/Disk/config.plist b/Modules/Disk/config.plist index 0a423868..8aff0232 100644 --- a/Modules/Disk/config.plist +++ b/Modules/Disk/config.plist @@ -21,6 +21,10 @@ Value 0.36 + Unsupported colors + + pressure + bar_chart @@ -40,11 +44,13 @@ Box - Color - Value 0.36 + Unsupported colors + + pressure + disk diff --git a/Modules/Disk/popup.swift b/Modules/Disk/popup.swift index e76d8c7e..a3b48b39 100644 --- a/Modules/Disk/popup.swift +++ b/Modules/Disk/popup.swift @@ -27,7 +27,9 @@ internal class Popup: NSView { internal func usageCallback(_ value: DiskList) { if self.list.count != value.list.count { - self.subviews.forEach{ $0.removeFromSuperview() } + DispatchQueue.main.async(execute: { + self.subviews.forEach{ $0.removeFromSuperview() } + }) self.list = [:] } diff --git a/Modules/Memory/main.swift b/Modules/Memory/main.swift index 9ca4db51..866bad16 100644 --- a/Modules/Memory/main.swift +++ b/Modules/Memory/main.swift @@ -67,6 +67,7 @@ public class Memory: Module { self.popupView.loadCallback(value!) if let widget = self.widget as? Mini { widget.setValue(value!.usage ?? 0, sufix: "%") + widget.setPressure(value?.pressureLevel ?? 0) } if let widget = self.widget as? LineChart { widget.setValue(value!.usage ?? 0) diff --git a/StatsKit/extensions.swift b/StatsKit/extensions.swift index bbd9f6c4..b91a8e1c 100644 --- a/StatsKit/extensions.swift +++ b/StatsKit/extensions.swift @@ -366,6 +366,42 @@ public extension NSView { return row } + + func SelectColorRow(frame: NSRect, title: String, action: Selector, items: [String], selected: String) -> NSView { + let row: NSView = NSView(frame: frame) + + let rowTitle: NSTextField = LabelField(frame: NSRect(x: 0, y: (row.frame.height - 16)/2, width: row.frame.width - 52, height: 17), title) + rowTitle.font = NSFont.systemFont(ofSize: 13, weight: .light) + rowTitle.textColor = .textColor + + let select: NSPopUpButton = NSPopUpButton(frame: NSRect(x: row.frame.width - 50, y: 0, width: 50, height: row.frame.height)) + select.target = self + select.action = action + + let menu = NSMenu() + items.forEach { (color: String) in + if color.contains("separator") { + menu.addItem(NSMenuItem.separator()) + } else { + let interfaceMenu = NSMenuItem(title: color, action: nil, keyEquivalent: "") + menu.addItem(interfaceMenu) + if selected == color { + interfaceMenu.state = .on + } + } + } + + select.menu = menu + select.sizeToFit() + + rowTitle.setFrameSize(NSSize(width: row.frame.width - select.frame.width, height: rowTitle.frame.height)) + select.setFrameOrigin(NSPoint(x: row.frame.width - select.frame.width, y: 0)) + + row.addSubview(select) + row.addSubview(rowTitle) + + return row + } } public extension Notification.Name { @@ -612,3 +648,66 @@ public func asyncShell(_ args: String) { task.standardOutput = pipe task.launch() } + +public func colorFromString(_ colorString: String) -> NSColor { + switch colorString { + case "black": + return NSColor.black + case "darkGray": + return NSColor.darkGray + case "lightGray": + return NSColor.lightGray + case "gray": + return NSColor.gray + case "secondGray": + return NSColor.systemGray + case "white": + return NSColor.white + case "red": + return NSColor.red + case "secondRed": + return NSColor.systemRed + case "green": + return NSColor.green + case "secondGreen": + return NSColor.systemGreen + case "blue": + return NSColor.blue + case "secondBlue": + return NSColor.systemBlue + case "yellow": + return NSColor.yellow + case "secondYellow": + return NSColor.systemYellow + case "orange": + return NSColor.orange + case "secondOrange": + return NSColor.systemOrange + case "purple": + return NSColor.purple + case "secondPurple": + return NSColor.systemPurple + case "brown": + return NSColor.brown + case "secondBrown": + return NSColor.systemBrown + case "cyan": + return NSColor.cyan + case "magenta": + return NSColor.magenta + case "clear": + return NSColor.clear + case "pink": + return NSColor.systemPink + case "teal": + return NSColor.systemTeal + case "indigo": + if #available(OSX 10.15, *) { + return NSColor.systemIndigo + } else { + return NSColor(hexString: "#4B0082") + } + default: + return NSColor.controlAccentColor + } +}