mirror of
https://github.com/morgan9e/macos-stats
synced 2026-04-15 00:34:08 +09:00
feat: initialized Tachometer widget (#631)
This commit is contained in:
@@ -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)
|
||||
|
||||
109
Kit/Widgets/Tachometer.swift
Normal file
109
Kit/Widgets/Tachometer.swift
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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 ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = "<group>"; };
|
||||
9A9B8C9C27149A3700218374 /* Tachometer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tachometer.swift; sourceTree = "<group>"; };
|
||||
9A9EA9442476D34500E3B883 /* Update.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Update.swift; sourceTree = "<group>"; };
|
||||
9AA6425F244B274200416A33 /* popup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = popup.swift; sourceTree = "<group>"; };
|
||||
9AAAE83524F953FC00CD92D7 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
@@ -672,6 +674,7 @@
|
||||
9A28475B2666AA2700EC1F6D /* Memory.swift */,
|
||||
9A28475E2666AA2700EC1F6D /* Sensors.swift */,
|
||||
9A28475C2666AA2700EC1F6D /* Speed.swift */,
|
||||
9A9B8C9C27149A3700218374 /* Tachometer.swift */,
|
||||
);
|
||||
path = Widgets;
|
||||
sourceTree = "<group>";
|
||||
@@ -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 */,
|
||||
|
||||
Reference in New Issue
Block a user