mirror of
https://github.com/morgan9e/macos-stats
synced 2026-04-14 00:04:15 +09:00
- add CircleChart
- add a dashboard to CPU popup (move chart below, add circle chart in the dashboard)
This commit is contained in:
@@ -18,7 +18,8 @@ internal class Popup: NSView {
|
||||
private var title: String
|
||||
|
||||
private let dashboardHeight: CGFloat = 90
|
||||
private let detailsHeight: CGFloat = 66 // -26
|
||||
private let chartHeight: CGFloat = 90
|
||||
private let detailsHeight: CGFloat = 88
|
||||
private let processesHeight: CGFloat = 22*5
|
||||
|
||||
private var loadField: NSTextField? = nil
|
||||
@@ -28,6 +29,7 @@ internal class Popup: NSView {
|
||||
private var userField: NSTextField? = nil
|
||||
private var idleField: NSTextField? = nil
|
||||
|
||||
private var circle: CircleGraphView? = nil
|
||||
private var chart: LineChartView? = nil
|
||||
private var ready: Bool = false
|
||||
|
||||
@@ -37,9 +39,15 @@ internal class Popup: NSView {
|
||||
self.store = store
|
||||
self.title = title
|
||||
|
||||
super.init(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: dashboardHeight + (Constants.Popup.separatorHeight*2) + detailsHeight + processesHeight))
|
||||
super.init(frame: NSRect(
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: Constants.Popup.width,
|
||||
height: dashboardHeight + chartHeight + detailsHeight + processesHeight + (Constants.Popup.separatorHeight*3)
|
||||
))
|
||||
|
||||
initDashboard()
|
||||
initChart()
|
||||
initDetails()
|
||||
initProcesses()
|
||||
}
|
||||
@@ -53,33 +61,43 @@ internal class Popup: NSView {
|
||||
}
|
||||
|
||||
private func initDashboard() {
|
||||
let rightWidth: CGFloat = 110
|
||||
let view: NSView = NSView(frame: NSRect(x: 0, y: self.frame.height - self.dashboardHeight, width: self.frame.width, height: self.dashboardHeight))
|
||||
|
||||
let leftPanel = NSView(frame: NSRect(x: 0, y: 0, width: view.frame.width - rightWidth - Constants.Popup.margins, height: view.frame.height))
|
||||
let container: NSView = NSView(frame: NSRect(x: 0, y: 20/2, width: view.frame.width, height: self.dashboardHeight-20))
|
||||
let circle: CircleGraphView = CircleGraphView(frame: NSRect(x: 0, y: 0, width: container.frame.width, height: container.frame.height), segments: [])
|
||||
self.circle = circle
|
||||
container.addSubview(circle)
|
||||
view.addSubview(container)
|
||||
|
||||
self.chart = LineChartView(frame: NSRect(x: 4, y: 3, width: leftPanel.frame.width, height: leftPanel.frame.height), num: 120)
|
||||
leftPanel.addSubview(self.chart!)
|
||||
self.addSubview(view)
|
||||
}
|
||||
|
||||
private func initChart() {
|
||||
let y: CGFloat = self.frame.height - self.dashboardHeight - Constants.Popup.separatorHeight
|
||||
let separator = SeparatorView("History", origin: NSPoint(x: 0, y: y), width: self.frame.width)
|
||||
self.addSubview(separator)
|
||||
|
||||
let rightPanel: NSView = NSView(frame: NSRect(x: view.frame.width - rightWidth, y: 0, width: rightWidth, height: view.frame.height))
|
||||
self.loadField = addFirstRow(mView: rightPanel, y: ((rightPanel.frame.height - 16)/2)+9, title: "Load:", value: "")
|
||||
self.temperatureField = addFirstRow(mView: rightPanel, y: ((rightPanel.frame.height - 16)/2)-9, title: "Temperature:", value: "")
|
||||
let view: NSView = NSView(frame: NSRect(x: 0, y: y - self.chartHeight, width: self.frame.width, height: self.chartHeight))
|
||||
view.wantsLayer = true
|
||||
view.layer?.backgroundColor = NSColor.lightGray.withAlphaComponent(0.1).cgColor
|
||||
view.layer?.cornerRadius = 3
|
||||
self.chart = LineChartView(frame: NSRect(x: 1, y: 0, width: view.frame.width, height: view.frame.height), num: 120)
|
||||
view.addSubview(self.chart!)
|
||||
|
||||
view.addSubview(leftPanel)
|
||||
view.addSubview(rightPanel)
|
||||
self.addSubview(view)
|
||||
}
|
||||
|
||||
private func initDetails() {
|
||||
let y: CGFloat = self.frame.height - self.dashboardHeight - Constants.Popup.separatorHeight
|
||||
let y: CGFloat = self.frame.height - self.dashboardHeight - self.chartHeight - (Constants.Popup.separatorHeight*2)
|
||||
let separator = SeparatorView("Details", origin: NSPoint(x: 0, y: y), width: self.frame.width)
|
||||
self.addSubview(separator)
|
||||
|
||||
let view: NSView = NSView(frame: NSRect(x: 0, y: separator.frame.origin.y - self.detailsHeight, width: self.frame.width, height: self.detailsHeight))
|
||||
|
||||
self.systemField = PopupRow(view, n: 2, title: "System:", value: "")
|
||||
self.userField = PopupRow(view, n: 1, title: "User:", value: "")
|
||||
self.idleField = PopupRow(view, n: 0, title: "Idle:", value: "")
|
||||
self.systemField = PopupWithColorRow(view, color: NSColor.systemRed, n: 3, title: "System:", value: "")
|
||||
self.userField = PopupWithColorRow(view, color: NSColor.systemBlue, n: 2, title: "User:", value: "")
|
||||
self.idleField = PopupWithColorRow(view, color: NSColor.lightGray.withAlphaComponent(0.5), n: 1, title: "Idle:", value: "")
|
||||
self.temperatureField = PopupRow(view, n: 0, title: "Temperature:", value: "")
|
||||
|
||||
self.addSubview(view)
|
||||
}
|
||||
@@ -144,6 +162,11 @@ internal class Popup: NSView {
|
||||
self.ready = true
|
||||
}
|
||||
|
||||
self.circle?.setValue(value.totalUsage)
|
||||
self.circle?.setSegments([
|
||||
circle_segment(value: value.systemLoad, color: NSColor.systemRed),
|
||||
circle_segment(value: value.userLoad, color: NSColor.systemBlue),
|
||||
])
|
||||
self.chart?.addValue(value.totalUsage)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -20,6 +20,16 @@ public enum chart_t: Int {
|
||||
}
|
||||
}
|
||||
|
||||
public struct circle_segment {
|
||||
let value: Double
|
||||
let color: NSColor
|
||||
|
||||
public init(value: Double, color: NSColor) {
|
||||
self.value = value
|
||||
self.color = color
|
||||
}
|
||||
}
|
||||
|
||||
public class LineChartView: NSView {
|
||||
public var points: [Double]? = nil
|
||||
public var transparent: Bool = true
|
||||
@@ -48,7 +58,7 @@ public class LineChartView: NSView {
|
||||
gradientColor = self.color.withAlphaComponent(0.8)
|
||||
}
|
||||
|
||||
let context = NSGraphicsContext.current!.cgContext
|
||||
guard let context = NSGraphicsContext.current?.cgContext else { return }
|
||||
context.setShouldAntialias(true)
|
||||
let height: CGFloat = self.frame.size.height - self.frame.origin.y - 0.5
|
||||
let xRatio: CGFloat = self.frame.size.width / CGFloat(self.points!.count)
|
||||
@@ -98,3 +108,78 @@ public class LineChartView: NSView {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CircleGraphView: NSView {
|
||||
private var value: Double? = nil
|
||||
private var segments: [circle_segment] = []
|
||||
|
||||
public init(frame: NSRect, segments: [circle_segment]) {
|
||||
self.segments = segments
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
public override func draw(_ rect: CGRect) {
|
||||
let arcWidth: CGFloat = 7.0
|
||||
let fullCircle = 2 * CGFloat.pi
|
||||
var segments = self.segments
|
||||
let totalAmount = segments.reduce(0) { $0 + $1.value }
|
||||
if totalAmount < 1 {
|
||||
segments.append(circle_segment(value: Double(1-totalAmount), color: NSColor.lightGray.withAlphaComponent(0.5)))
|
||||
}
|
||||
|
||||
let centerPoint = CGPoint(x: rect.midX, y: rect.midY)
|
||||
let radius = (min(rect.width, rect.height) - arcWidth) / 2
|
||||
|
||||
guard let context = NSGraphicsContext.current?.cgContext else { return }
|
||||
context.setShouldAntialias(true)
|
||||
|
||||
context.setLineWidth(arcWidth)
|
||||
context.setLineCap(.butt)
|
||||
|
||||
let startAngle: CGFloat = CGFloat.pi/2
|
||||
var previousAngle = startAngle
|
||||
|
||||
for segment in segments.reversed() {
|
||||
let currentAngle: CGFloat = previousAngle + (CGFloat(segment.value) * fullCircle)
|
||||
|
||||
context.setStrokeColor(segment.color.cgColor)
|
||||
context.addArc(center: centerPoint, radius: radius, startAngle: previousAngle, endAngle: currentAngle, clockwise: false)
|
||||
context.strokePath()
|
||||
|
||||
previousAngle = currentAngle
|
||||
}
|
||||
|
||||
if let value = self.value {
|
||||
let stringAttributes = [
|
||||
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 15, weight: .regular),
|
||||
NSAttributedString.Key.foregroundColor: isDarkMode ? NSColor.white : NSColor.textColor,
|
||||
NSAttributedString.Key.paragraphStyle: NSMutableParagraphStyle()
|
||||
]
|
||||
|
||||
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 str = NSAttributedString.init(string: percentage, attributes: stringAttributes)
|
||||
str.draw(with: rect)
|
||||
}
|
||||
}
|
||||
|
||||
public func setValue(_ value: Double) {
|
||||
self.value = value
|
||||
if self.window?.isVisible ?? true {
|
||||
self.display()
|
||||
}
|
||||
}
|
||||
|
||||
public func setSegments(_ segments: [circle_segment]) {
|
||||
self.segments = segments
|
||||
if self.window?.isVisible ?? true {
|
||||
self.display()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -620,6 +620,25 @@ public func PopupRow(_ view: NSView, n: CGFloat, title: String, value: String) -
|
||||
return valueView
|
||||
}
|
||||
|
||||
public func PopupWithColorRow(_ view: NSView, color: NSColor, n: CGFloat, title: String, value: String) -> ValueField {
|
||||
let rowView: NSView = NSView(frame: NSRect(x: 0, y: 22*n, width: view.frame.width, height: 22))
|
||||
|
||||
let colorView: NSView = NSView(frame: NSRect(x: 2, y: 5, width: 12, height: 12))
|
||||
colorView.wantsLayer = true
|
||||
colorView.layer?.backgroundColor = color.cgColor
|
||||
colorView.layer?.cornerRadius = 2
|
||||
let labelWidth = title.widthOfString(usingFont: .systemFont(ofSize: 13, weight: .regular)) + 5
|
||||
let labelView: LabelField = LabelField(frame: NSRect(x: 18, y: (22-15)/2, width: labelWidth, height: 15), title)
|
||||
let valueView: ValueField = ValueField(frame: NSRect(x: 18 + labelWidth, y: (22-16)/2, width: rowView.frame.width - labelWidth - 18, height: 16), value)
|
||||
|
||||
rowView.addSubview(colorView)
|
||||
rowView.addSubview(labelView)
|
||||
rowView.addSubview(valueView)
|
||||
view.addSubview(rowView)
|
||||
|
||||
return valueView
|
||||
}
|
||||
|
||||
public extension Array where Element : Equatable {
|
||||
func allEqual() -> Bool {
|
||||
if let firstElem = first {
|
||||
|
||||
Reference in New Issue
Block a user