Merge pull request #207 from exelban/pie-widget

Pie widget
This commit is contained in:
Serhiy Mytrovtsiy
2020-12-01 17:23:14 +01:00
committed by GitHub
13 changed files with 250 additions and 10 deletions

View File

@@ -34,6 +34,7 @@ public struct Widget_c_s {
}
}
public let margin: CGPoint = CGPoint(x: 2, y: 2)
public let spacing: CGFloat = 2
}
public struct Constants {

View File

@@ -0,0 +1,142 @@
//
// PieChart.swift
// ModuleKit
//
// Created by Serhiy Mytrovtsiy on 30/11/2020.
// Using Swift 5.0.
// Running on macOS 10.15.
//
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
//
import Cocoa
import StatsKit
public class PieChart: Widget {
private var labelState: Bool = true
private let store: UnsafePointer<Store>?
private var chart: PieChartView = PieChartView(
frame: NSRect(
x: Constants.Widget.margin.x,
y: Constants.Widget.margin.y,
width: Constants.Widget.height,
height: Constants.Widget.height
),
segments: [], filled: true, drawValue: false
)
private var labelView: NSView? = nil
private let size: CGFloat = Constants.Widget.height - (Constants.Widget.margin.y*2) + (Constants.Widget.margin.x*2)
public init(preview: Bool, title: String, config: NSDictionary?, store: UnsafePointer<Store>?) {
var widgetTitle: String = title
self.store = store
if config != nil {
if let titleFromConfig = config!["Title"] as? String {
widgetTitle = titleFromConfig
}
}
super.init(frame: CGRect(
x: Constants.Widget.margin.x,
y: Constants.Widget.margin.y,
width: self.size,
height: Constants.Widget.height - (Constants.Widget.margin.y*2)
))
self.preview = preview
self.title = widgetTitle
self.type = .pieChart
self.wantsLayer = true
self.canDrawConcurrently = true
if let store = self.store {
self.labelState = store.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_label", defaultValue: self.labelState)
}
if self.preview {
if self.title == "CPU" {
self.chart.setSegments([
circle_segment(value: 0.16, color: NSColor.systemRed),
circle_segment(value: 0.28, color: NSColor.systemBlue)
])
} else if self.title == "RAM" {
self.chart.setSegments([
circle_segment(value: 0.36, color: NSColor.systemBlue),
circle_segment(value: 0.12, color: NSColor.systemOrange),
circle_segment(value: 0.08, color: NSColor.systemPink)
])
}
}
self.draw()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func draw() {
let x: CGFloat = self.labelState ? 8 + Constants.Widget.spacing : 0
self.labelView = WidgetLabelView(self.title, height: self.frame.height)
self.labelView!.isHidden = !self.labelState
self.addSubview(self.labelView!)
self.addSubview(self.chart)
var frame = self.chart.frame
frame = NSRect(x: x, y: 0, width: self.frame.size.height, height: self.frame.size.height)
self.chart.frame = frame
self.setFrameSize(NSSize(width: self.size + x, height: self.frame.size.height))
}
public func setValue(_ segments: [circle_segment]) {
DispatchQueue.main.async(execute: {
self.chart.setSegments(segments)
})
}
// MARK: - Settings
public override func settings(superview: NSView) {
let rowHeight: CGFloat = 30
let height: CGFloat = ((rowHeight + Constants.Settings.margin) * 1) + 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)
))
view.addSubview(ToggleTitleRow(
frame: NSRect(x: 0, y: 0, width: view.frame.width, height: rowHeight),
title: LocalizedString("Label"),
action: #selector(toggleLabel),
state: self.labelState
))
superview.addSubview(view)
}
@objc private func toggleLabel(_ 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.labelState = state! == .on ? true : false
self.store?.pointee.set(key: "\(self.title)_\(self.type.rawValue)_label", value: self.labelState)
let x = self.labelState ? 6 + Constants.Widget.spacing : 0
self.labelView!.isHidden = !self.labelState
self.chart.setFrameOrigin(NSPoint(x: x, y: 0))
self.setWidth(self.labelState ? self.size+x : self.size)
}
}

View File

@@ -285,6 +285,7 @@ open class Settings: NSView, Settings_p {
self.widgetSettingsView?.removeFromSuperview()
self.moduleSettingsView?.removeFromSuperview()
self.widgetSettingsView = nil
self.addWidgetSettings()
if self.moduleSettings != nil {

View File

@@ -57,6 +57,7 @@ public enum widget_t: String {
case mini = "mini"
case lineChart = "line_chart"
case barChart = "bar_chart"
case pieChart = "pie_chart"
case speed = "speed"
case battery = "battery"
case sensors = "sensors"
@@ -83,6 +84,7 @@ open class Widget: NSView, Widget_p {
case .mini: return "Mini"
case .lineChart: return "Line chart"
case .barChart: return "Bar chart"
case .pieChart: return "Pie chart"
case .speed: return "Speed"
case .battery: return "Battery"
case .sensors: return "Text"
@@ -141,6 +143,9 @@ func LoadWidget(_ type: widget_t, preview: Bool, name: String, config: NSDiction
case .barChart:
widget = BarChart(preview: preview, title: name, config: widgetConfig, store: store)
break
case .pieChart:
widget = PieChart(preview: preview, title: name, config: widgetConfig, store: store)
break
case .speed:
widget = SpeedWidget(preview: preview, title: name, config: widgetConfig, store: store)
break

View File

@@ -55,6 +55,13 @@
<key>Order</key>
<integer>2</integer>
</dict>
<key>pie_chart</key>
<dict>
<key>Default</key>
<false/>
<key>Order</key>
<integer>3</integer>
</dict>
</dict>
</dict>
</plist>

View File

@@ -132,5 +132,11 @@ public class CPU: Module {
if let widget = self.widget as? BarChart {
widget.setValue(self.usagePerCoreState ? value!.usagePerCore : [value!.totalUsage])
}
if let widget = self.widget as? PieChart {
widget.setValue([
circle_segment(value: value!.systemLoad, color: NSColor.systemRed),
circle_segment(value: value!.userLoad, color: NSColor.systemBlue)
])
}
}
}

View File

@@ -31,7 +31,7 @@ internal class Popup: NSView, Popup_p {
private var idleField: NSTextField? = nil
private var chart: LineChartView? = nil
private var circle: CircleGraphView? = nil
private var circle: PieChartView? = nil
private var temperatureCircle: HalfCircleGraphView? = nil
private var frequencyCircle: HalfCircleGraphView? = nil
private var initialized: Bool = false
@@ -118,7 +118,12 @@ internal class Popup: NSView, Popup_p {
let view: NSView = NSView(frame: NSRect(x: 0, y: 0, width: self.frame.width, height: self.dashboardHeight))
let container: NSView = NSView(frame: NSRect(x: 0, y: 10, width: view.frame.width, height: self.dashboardHeight-20))
self.circle = CircleGraphView(frame: NSRect(x: (container.frame.width - container.frame.height)/2, y: 0, width: container.frame.height, height: container.frame.height), segments: [])
self.circle = PieChartView(frame: NSRect(
x: (container.frame.width - container.frame.height)/2,
y: 0,
width: container.frame.height,
height: container.frame.height
), segments: [], drawValue: true)
self.circle!.toolTip = LocalizedString("CPU usage")
container.addSubview(self.circle!)

View File

@@ -53,6 +53,13 @@
<key>Order</key>
<integer>2</integer>
</dict>
<key>pie_chart</key>
<dict>
<key>Default</key>
<false/>
<key>Order</key>
<integer>3</integer>
</dict>
<key>memory</key>
<dict>
<key>Default</key>
@@ -63,7 +70,7 @@
<string>51383185408,198466408448</string>
</dict>
<key>Order</key>
<integer>3</integer>
<integer>4</integer>
</dict>
</dict>
</dict>

View File

@@ -104,6 +104,14 @@ public class Memory: Module {
widget.setValue([value!.usage])
widget.setPressure(value?.pressureLevel ?? 0)
}
if let widget = self.widget as? PieChart {
let total: Double = value?.total ?? 1
widget.setValue([
circle_segment(value: value!.active/total, color: NSColor.systemBlue),
circle_segment(value: value!.wired/total, color: NSColor.systemOrange),
circle_segment(value: value!.compressed/total, color: NSColor.systemPink)
])
}
if let widget = self.widget as? MemoryWidget {
widget.setValue((Int64(value!.free), Int64(value!.used)))
}

View File

@@ -34,7 +34,7 @@ internal class Popup: NSView, Popup_p {
private var compressedField: NSTextField? = nil
private var chart: LineChartView? = nil
private var circle: CircleGraphView? = nil
private var circle: PieChartView? = nil
private var level: PressureView? = nil
private var initialized: Bool = false
private var processesInitialized: Bool = false
@@ -117,7 +117,12 @@ internal class Popup: NSView, Popup_p {
let view: NSView = NSView(frame: NSRect(x: 0, y: self.frame.height - self.dashboardHeight, width: self.frame.width, height: self.dashboardHeight))
let container: NSView = NSView(frame: NSRect(x: 0, y: 10, width: view.frame.width, height: self.dashboardHeight-20))
self.circle = CircleGraphView(frame: NSRect(x: (container.frame.width - container.frame.height)/2, y: 0, width: container.frame.height, height: container.frame.height), segments: [])
self.circle = PieChartView(frame: NSRect(
x: (container.frame.width - container.frame.height)/2,
y: 0,
width: container.frame.height,
height: container.frame.height
), segments: [], drawValue: true)
self.circle!.toolTip = LocalizedString("Memory usage")
container.addSubview(self.circle!)

View File

@@ -17,6 +17,7 @@
9A0C82EE2446124800FAE3D4 /* SystemKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7D0CB62444C2C800B09070 /* SystemKit.swift */; };
9A1A7ABA24561F0B00A84F7A /* BarChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1A7AB924561F0B00A84F7A /* BarChart.swift */; };
9A1D5E4B25235C8100B82BFC /* helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D5E4A25235C8100B82BFC /* helpers.swift */; };
9A20E6DA2575555100AC2302 /* PieChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A20E6D92575555100AC2302 /* PieChart.swift */; };
9A27D4FD2538A3E5001BB651 /* Repeat in Frameworks */ = {isa = PBXBuildFile; productRef = 9A27D4FC2538A3E5001BB651 /* Repeat */; };
9A27D5352538A456001BB651 /* Reachability in Frameworks */ = {isa = PBXBuildFile; productRef = 9A27D5342538A456001BB651 /* Reachability */; };
9A34353B243E278D006B19F9 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A34353A243E278D006B19F9 /* main.swift */; };
@@ -376,6 +377,7 @@
9A141101229E721200D29793 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
9A1A7AB924561F0B00A84F7A /* BarChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarChart.swift; sourceTree = "<group>"; };
9A1D5E4A25235C8100B82BFC /* helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = helpers.swift; sourceTree = "<group>"; };
9A20E6D92575555100AC2302 /* PieChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PieChart.swift; sourceTree = "<group>"; };
9A27D4A925389EFD001BB651 /* Stats.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Stats.entitlements; sourceTree = "<group>"; };
9A343527243E26A0006B19F9 /* LaunchAtLogin.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LaunchAtLogin.app; sourceTree = BUILT_PRODUCTS_DIR; };
9A343535243E26A0006B19F9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -697,6 +699,7 @@
9A7C61B32440DF810032695D /* Mini.swift */,
9AA64263244B94F300416A33 /* LineChart.swift */,
9A1A7AB924561F0B00A84F7A /* BarChart.swift */,
9A20E6D92575555100AC2302 /* PieChart.swift */,
9A3E17E7247AA8E100449CD1 /* Speed.swift */,
9ABFF911248BF39500C9041A /* Battery.swift */,
9AE29AFD249A82B70071B02D /* Sensors.swift */,
@@ -1498,6 +1501,7 @@
9A3E17E8247AA8E100449CD1 /* Speed.swift in Sources */,
9AA64264244B94F300416A33 /* LineChart.swift in Sources */,
9A1A7ABA24561F0B00A84F7A /* BarChart.swift in Sources */,
9A20E6DA2575555100AC2302 /* PieChart.swift in Sources */,
9A944D55244920690058F32A /* reader.swift in Sources */,
9A7C61B42440DF810032695D /* Mini.swift in Sources */,
9AE29AFE249A82B70071B02D /* Sensors.swift in Sources */,

View File

@@ -227,12 +227,18 @@ public class NetworkChartView: NSView {
}
}
public class CircleGraphView: NSView {
public class PieChartView: NSView {
private var filled: Bool = false
private var drawValue: Bool = false
private var value: Double? = nil
private var segments: [circle_segment] = []
public init(frame: NSRect, segments: [circle_segment]) {
public init(frame: NSRect, segments: [circle_segment], filled: Bool = false, drawValue: Bool = false) {
self.filled = filled
self.drawValue = drawValue
self.segments = segments
super.init(frame: frame)
}
@@ -241,7 +247,7 @@ public class CircleGraphView: NSView {
}
public override func draw(_ rect: CGRect) {
let arcWidth: CGFloat = 7.0
let arcWidth: CGFloat = self.filled ? min(rect.width, rect.height) / 2 : 7
let fullCircle = 2 * CGFloat.pi
var segments = self.segments
let totalAmount = segments.reduce(0) { $0 + $1.value }
@@ -271,7 +277,7 @@ public class CircleGraphView: NSView {
previousAngle = currentAngle
}
if let value = self.value {
if let value = self.value, self.drawValue {
let stringAttributes = [
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 15, weight: .regular),
NSAttributedString.Key.foregroundColor: isDarkMode ? NSColor.white : NSColor.textColor,
@@ -280,7 +286,7 @@ public class CircleGraphView: NSView {
let percentage = "\(Int(value*100))%"
let width: CGFloat = percentage.widthOfString(usingFont: NSFont.systemFont(ofSize: 15))
let rect = CGRect(x: (self.frame.width-width)/2, y: (self.frame.height-12)/2, width: width, height: 12)
let rect = CGRect(x: (self.frame.width-width)/2, y: (self.frame.height-11)/2, width: width, height: 12)
let str = NSAttributedString.init(string: percentage, attributes: stringAttributes)
str.draw(with: rect)
}

View File

@@ -780,3 +780,46 @@ public class CAText: CATextLayer {
return value.widthOfString(usingFont: self.font as! NSFont).rounded(.up) + add
}
}
public class WidgetLabelView: NSView {
private var title: String
public init(_ title: String, height: CGFloat) {
self.title = title
super.init(frame: NSRect(
x: 0,
y: 0,
width: 6,
height: height
))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
let style = NSMutableParagraphStyle()
style.alignment = .center
let stringAttributes = [
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 7, weight: .regular),
NSAttributedString.Key.foregroundColor: NSColor.textColor,
NSAttributedString.Key.paragraphStyle: style
]
let title = self.title.prefix(3)
let letterHeight = self.frame.height / 3
let letterWidth: CGFloat = self.frame.height / CGFloat(title.count)
var yMargin: CGFloat = 0
for char in title.uppercased().reversed() {
let rect = CGRect(x: 0, y: yMargin, width: letterWidth, height: letterHeight-1)
let str = NSAttributedString.init(string: "\(char)", attributes: stringAttributes)
str.draw(with: rect)
yMargin += letterHeight
}
}
}