diff --git a/Modules/CPU/popup.swift b/Modules/CPU/popup.swift index 11f389ca..5a74e21f 100644 --- a/Modules/CPU/popup.swift +++ b/Modules/CPU/popup.swift @@ -19,18 +19,18 @@ internal class Popup: NSView { private let dashboardHeight: CGFloat = 90 private let chartHeight: CGFloat = 90 - private let detailsHeight: CGFloat = 88 + private let detailsHeight: CGFloat = 22*3 private let processesHeight: CGFloat = 22*5 private var loadField: NSTextField? = nil - private var temperatureField: NSTextField? = nil private var systemField: NSTextField? = nil private var userField: NSTextField? = nil private var idleField: NSTextField? = nil - private var circle: CircleGraphView? = nil private var chart: LineChartView? = nil + private var circle: CircleGraphView? = nil + private var temperatureCircle: HalfCircleGraphView? = nil private var ready: Bool = false private var processes: [ProcessView] = [] @@ -62,11 +62,19 @@ internal class Popup: NSView { private func initDashboard() { let view: NSView = NSView(frame: NSRect(x: 0, y: self.frame.height - self.dashboardHeight, width: self.frame.width, height: self.dashboardHeight)) + view.wantsLayer = true - 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) + 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!.toolTip = "CPU usage" + container.addSubview(self.circle!) + + let centralWidth: CGFloat = self.dashboardHeight-20 + let sideWidth: CGFloat = (view.frame.width - centralWidth - (Constants.Popup.margins*2))/2 + self.temperatureCircle = HalfCircleGraphView(frame: NSRect(x: (sideWidth - 50)/2, y: 10, width: 50, height: 50)) + self.temperatureCircle!.toolTip = "CPU temperature" + + view.addSubview(self.temperatureCircle!) view.addSubview(container) self.addSubview(view) @@ -81,7 +89,9 @@ internal class Popup: NSView { 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!) self.addSubview(view) @@ -94,10 +104,9 @@ internal class Popup: NSView { let view: NSView = NSView(frame: NSRect(x: 0, y: separator.frame.origin.y - self.detailsHeight, width: self.frame.width, height: self.detailsHeight)) - 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.systemField = PopupWithColorRow(view, color: NSColor.systemRed, n: 2, title: "System:", value: "") + self.userField = PopupWithColorRow(view, color: NSColor.systemBlue, n: 1, title: "User:", value: "") + self.idleField = PopupWithColorRow(view, color: NSColor.lightGray.withAlphaComponent(0.5), n: 0, title: "Idle:", value: "") self.addSubview(view) } @@ -141,18 +150,8 @@ internal class Popup: NSView { } public func loadCallback(_ value: CPU_Load, tempValue: Double?) { - var temperature: String = "" - DispatchQueue.main.async(execute: { if (self.window?.isVisible ?? false) || !self.ready { - if tempValue != nil { - let formatter = MeasurementFormatter() - let measurement = Measurement(value: tempValue!.rounded(toPlaces: 0), unit: UnitTemperature.celsius) - temperature = formatter.string(from: measurement) - } - - self.temperatureField?.stringValue = temperature - self.systemField?.stringValue = "\(Int(value.systemLoad.rounded(toPlaces: 2) * 100)) %" self.userField?.stringValue = "\(Int(value.userLoad.rounded(toPlaces: 2) * 100)) %" self.idleField?.stringValue = "\(Int(value.idleLoad.rounded(toPlaces: 2) * 100)) %" @@ -167,6 +166,14 @@ internal class Popup: NSView { circle_segment(value: value.systemLoad, color: NSColor.systemRed), circle_segment(value: value.userLoad, color: NSColor.systemBlue), ]) + if tempValue != nil { + self.temperatureCircle?.setValue(tempValue!) + + let formatter = MeasurementFormatter() + formatter.numberFormatter.maximumFractionDigits = 0 + let measurement = Measurement(value: tempValue!, unit: UnitTemperature.celsius) + self.temperatureCircle?.setText(formatter.string(from: measurement)) + } self.chart?.addValue(value.totalUsage) }) } diff --git a/StatsKit/Charts.swift b/StatsKit/Charts.swift index 30754a45..7c518ba1 100644 --- a/StatsKit/Charts.swift +++ b/StatsKit/Charts.swift @@ -163,7 +163,6 @@ 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 str = NSAttributedString.init(string: percentage, attributes: stringAttributes) str.draw(with: rect) } @@ -183,3 +182,76 @@ public class CircleGraphView: NSView { } } } + +public class HalfCircleGraphView: NSView { + private var value: Double = 0.1 + private var text: String? = nil + + public var color: NSColor = NSColor.systemBlue + + public override func draw(_ rect: CGRect) { + let arcWidth: CGFloat = 7.0 + 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) + + var segments: [circle_segment] = [ + circle_segment(value: self.value, color: self.color) + ] + if self.value < 1 { + segments.append(circle_segment(value: Double(1-self.value), color: NSColor.lightGray.withAlphaComponent(0.5))) + } + + let startAngle: CGFloat = -(1/4)*CGFloat.pi + let endCircle: CGFloat = (7/4)*CGFloat.pi - (1/4)*CGFloat.pi + var previousAngle = startAngle + + context.saveGState() + context.translateBy(x: rect.width, y: 0) + context.scaleBy(x: -1, y: 1) + + for segment in segments { + let currentAngle: CGFloat = previousAngle + (CGFloat(segment.value) * endCircle) + + context.setStrokeColor(segment.color.cgColor) + context.addArc(center: centerPoint, radius: radius, startAngle: previousAngle, endAngle: currentAngle, clockwise: false) + context.strokePath() + + previousAngle = currentAngle + } + + context.restoreGState() + + if self.text != nil { + let stringAttributes = [ + NSAttributedString.Key.font: NSFont.systemFont(ofSize: 10, weight: .regular), + NSAttributedString.Key.foregroundColor: isDarkMode ? NSColor.white : NSColor.textColor, + NSAttributedString.Key.paragraphStyle: NSMutableParagraphStyle() + ] + + let width: CGFloat = self.text!.widthOfString(usingFont: NSFont.systemFont(ofSize: 10)) + let rect = CGRect(x: (self.frame.width-width)/2, y: (self.frame.height-6)/2, width: width, height: 6) + let str = NSAttributedString.init(string: self.text!, attributes: stringAttributes) + str.draw(with: rect) + } + } + + public func setValue(_ value: Double) { + self.value = value > 1 ? value/100 : value + if self.window?.isVisible ?? true { + self.display() + } + } + + public func setText(_ value: String) { + self.text = value + if self.window?.isVisible ?? true { + self.display() + } + } +}