mirror of
https://github.com/morgan9e/macos-stats
synced 2026-04-14 00:04:15 +09:00
@@ -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 {
|
||||
|
||||
142
ModuleKit/Widgets/PieChart.swift
Normal file
142
ModuleKit/Widgets/PieChart.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!)
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
|
||||
@@ -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!)
|
||||
|
||||
|
||||
@@ -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 */,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user