From 9163b35ecf0c4e5ee60e07a59741614f679127b8 Mon Sep 17 00:00:00 2001 From: Serhiy Mytrovtsiy Date: Tue, 12 Oct 2021 17:30:52 +0200 Subject: [PATCH] feat: initialized Tachometer widget (#631) --- Kit/Widgets/PieChart.swift | 4 +- Kit/Widgets/Tachometer.swift | 109 ++++++++++++++++++++++++++++++++ Kit/module/widget.swift | 5 ++ Kit/plugins/Charts.swift | 69 ++++++++++++++++++++ Stats.xcodeproj/project.pbxproj | 4 ++ 5 files changed, 188 insertions(+), 3 deletions(-) create mode 100644 Kit/Widgets/Tachometer.swift diff --git a/Kit/Widgets/PieChart.swift b/Kit/Widgets/PieChart.swift index a64684de..93bb0dfc 100644 --- a/Kit/Widgets/PieChart.swift +++ b/Kit/Widgets/PieChart.swift @@ -77,9 +77,7 @@ public class PieChart: WidgetWrapper { 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.chart.setFrame(NSRect(x: x, y: 0, width: self.frame.size.height, height: self.frame.size.height)) self.setFrameSize(NSSize(width: self.size + x, height: self.frame.size.height)) self.setWidth(self.size + x) diff --git a/Kit/Widgets/Tachometer.swift b/Kit/Widgets/Tachometer.swift new file mode 100644 index 00000000..41d78dc9 --- /dev/null +++ b/Kit/Widgets/Tachometer.swift @@ -0,0 +1,109 @@ +// +// Tachometer.swift +// Kit +// +// Created by Serhiy Mytrovtsiy on 11/10/2021. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2021 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa + +public class Tachometer: WidgetWrapper { + private var labelState: Bool = false + + private var chart: TachometerGraphView = TachometerGraphView( + frame: NSRect( + x: Constants.Widget.margin.x, + y: Constants.Widget.margin.y, + width: Constants.Widget.height, + height: Constants.Widget.height + ), segments: [] + ) + private var labelView: NSView? = nil + + private let size: CGFloat = Constants.Widget.height - (Constants.Widget.margin.y*2) + (Constants.Widget.margin.x*2) + + public init(title: String, config: NSDictionary?, preview: Bool = false) { + let widgetTitle: String = title + + super.init(.battery, title: widgetTitle, frame: CGRect( + x: Constants.Widget.margin.x, + y: Constants.Widget.margin.y, + width: self.size, + height: Constants.Widget.height - (2*Constants.Widget.margin.y) + )) + + self.canDrawConcurrently = true + + if preview { + self.chart.setSegments([ + circle_segment(value: 0.20, color: NSColor.systemRed), + circle_segment(value: 0.57, color: NSColor.systemBlue) + ]) + } else { + self.labelState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_label", defaultValue: self.labelState) + } + + 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) + + self.chart.setFrame(NSRect(x: x, y: 0, width: self.frame.size.height, height: self.frame.size.height)) + + self.setFrameSize(NSSize(width: self.size + x, height: self.frame.size.height)) + self.setWidth(self.size + x) + } + + public func setValue(_ segments: [circle_segment]) { + DispatchQueue.main.async(execute: { + self.chart.setSegments(segments) + }) + } + + // MARK: - Settings + + public override func settings(width: CGFloat) -> NSView { + let view = SettingsContainerView(width: width) + + view.addArrangedSubview(toggleTitleRow( + frame: NSRect(x: 0, y: 0, width: view.frame.width, height: Constants.Settings.row), + title: localizedString("Label"), + action: #selector(toggleLabel), + state: self.labelState + )) + + return 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 + Store.shared.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) + } +} diff --git a/Kit/module/widget.swift b/Kit/module/widget.swift index c9dabf72..45d6f75e 100644 --- a/Kit/module/widget.swift +++ b/Kit/module/widget.swift @@ -23,6 +23,7 @@ public enum widget_t: String { case sensors = "sensors" case memory = "memory" case label = "label" + case tachometer = "tachometer" public func new(module: String, config: NSDictionary, defaultWidget: widget_t) -> Widget? { var preview: widget_p? = nil @@ -62,6 +63,9 @@ public enum widget_t: String { case .label: preview = Label(title: module, config: widgetConfig, preview: true) item = Label(title: module, config: widgetConfig, preview: false) + case .tachometer: + preview = Tachometer(title: module, config: widgetConfig, preview: true) + item = Tachometer(title: module, config: widgetConfig, preview: false) default: break } @@ -84,6 +88,7 @@ public enum widget_t: String { case .sensors: return localizedString("Text widget") case .memory: return localizedString("Memory widget") case .label: return localizedString("Label widget") + case .tachometer: return localizedString("Tachometer widget") default: return "" } } diff --git a/Kit/plugins/Charts.swift b/Kit/plugins/Charts.swift index c7b8b6c4..c665ffa0 100644 --- a/Kit/plugins/Charts.swift +++ b/Kit/plugins/Charts.swift @@ -316,6 +316,12 @@ public class PieChartView: NSView { self.display() } } + + public func setFrame(_ frame: NSRect) { + var original = self.frame + original = frame + self.frame = original + } } public class HalfCircleGraphView: NSView { @@ -395,3 +401,66 @@ public class HalfCircleGraphView: NSView { } } } + +public class TachometerGraphView: NSView { + private var filled: Bool + private var segments: [circle_segment] + + public init(frame: NSRect, segments: [circle_segment], filled: Bool = true) { + self.filled = filled + 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 = self.filled ? min(rect.width, rect.height) / 2 : 7 + 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) + + context.translateBy(x: rect.width, y: -4) + context.scaleBy(x: -1, y: 1) + + let startAngle: CGFloat = 0 + let endCircle: CGFloat = CGFloat.pi + var previousAngle = startAngle + + 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 + } + } + + public func setSegments(_ segments: [circle_segment]) { + self.segments = segments + if self.window?.isVisible ?? true { + self.display() + } + } + + public func setFrame(_ frame: NSRect) { + var original = self.frame + original = frame + self.frame = original + } +} diff --git a/Stats.xcodeproj/project.pbxproj b/Stats.xcodeproj/project.pbxproj index 98802258..2ce2f6d3 100644 --- a/Stats.xcodeproj/project.pbxproj +++ b/Stats.xcodeproj/project.pbxproj @@ -102,6 +102,7 @@ 9A97CEFB253733F300742D8F /* settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A97CEFA253733F300742D8F /* settings.swift */; }; 9A97CF002537340400742D8F /* config.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9A97CEFF2537340400742D8F /* config.plist */; }; 9A9B25BB24F7DE2B00C3CCE6 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9A9B25BD24F7DE2B00C3CCE6 /* Localizable.strings */; }; + 9A9B8C9D27149A3700218374 /* Tachometer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A9B8C9C27149A3700218374 /* Tachometer.swift */; }; 9A9EA9452476D34500E3B883 /* Update.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A9EA9442476D34500E3B883 /* Update.swift */; }; 9AA64260244B274200416A33 /* popup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA6425F244B274200416A33 /* popup.swift */; }; 9AABEB7A243FD26200668CB0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AABEB79243FD26200668CB0 /* AppDelegate.swift */; }; @@ -429,6 +430,7 @@ 9A998CD722A199920087ADE7 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 9A998CD922A199970087ADE7 /* ServiceManagement.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ServiceManagement.framework; path = System/Library/Frameworks/ServiceManagement.framework; sourceTree = SDKROOT; }; 9A9B25BC24F7DE2B00C3CCE6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 9A9B8C9C27149A3700218374 /* Tachometer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tachometer.swift; sourceTree = ""; }; 9A9EA9442476D34500E3B883 /* Update.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Update.swift; sourceTree = ""; }; 9AA6425F244B274200416A33 /* popup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = popup.swift; sourceTree = ""; }; 9AAAE83524F953FC00CD92D7 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; @@ -672,6 +674,7 @@ 9A28475B2666AA2700EC1F6D /* Memory.swift */, 9A28475E2666AA2700EC1F6D /* Sensors.swift */, 9A28475C2666AA2700EC1F6D /* Speed.swift */, + 9A9B8C9C27149A3700218374 /* Tachometer.swift */, ); path = Widgets; sourceTree = ""; @@ -1527,6 +1530,7 @@ 9A28480E2666AB3000EC1F6D /* Updater.swift in Sources */, 9A2847622666AA2700EC1F6D /* Label.swift in Sources */, 9A28477C2666AA5000EC1F6D /* reader.swift in Sources */, + 9A9B8C9D27149A3700218374 /* Tachometer.swift in Sources */, 9A8AE0A326921A2A00B13054 /* Server.swift in Sources */, 9A2847652666AA2700EC1F6D /* Memory.swift in Sources */, 9A2847642666AA2700EC1F6D /* Battery.swift in Sources */,