mirror of
https://github.com/morgan9e/macos-stats
synced 2026-04-15 00:34:08 +09:00
feat: added new BarChart and replaced all custom bars with new chart
This commit is contained in:
@@ -142,7 +142,7 @@ public class LineChartView: NSView {
|
||||
private var cursor: NSPoint? = nil
|
||||
private var stop: Bool = false
|
||||
|
||||
public init(frame: NSRect, num: Int, suffix: String = "%", color: NSColor = .controlAccentColor, scale: Scale = .none, fixedScale: Double = 1, zeroValue: Double = 0.01) {
|
||||
public init(frame: NSRect = .zero, num: Int, suffix: String = "%", color: NSColor = .controlAccentColor, scale: Scale = .none, fixedScale: Double = 1, zeroValue: Double = 0.01) {
|
||||
self.points = Array(repeating: nil, count: max(num, 1))
|
||||
self.suffix = suffix
|
||||
self.color = color
|
||||
@@ -233,7 +233,7 @@ public class LineChartView: NSView {
|
||||
}
|
||||
|
||||
let point = CGPoint(
|
||||
x: (CGFloat(i) * xRatio) + dirtyRect.origin.x,
|
||||
x: CGFloat(i) * xRatio,
|
||||
y: y
|
||||
)
|
||||
line.append(point)
|
||||
@@ -1017,3 +1017,94 @@ public class GridChartView: NSView {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class BarChartView: NSView {
|
||||
private var values: [ColorValue] = []
|
||||
private var cursor: CGPoint? = nil
|
||||
private var queue: DispatchQueue = DispatchQueue(label: "eu.exelban.Stats.charts.bar")
|
||||
|
||||
private var size: CGFloat?
|
||||
private var horizontal: Bool
|
||||
|
||||
public init(frame: NSRect = NSRect.zero, size: CGFloat? = nil, horizontal: Bool = false) {
|
||||
self.size = size
|
||||
self.horizontal = horizontal
|
||||
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
public override func draw(_ dirtyRect: NSRect) {
|
||||
var widthHeight: CGFloat? = nil
|
||||
var isHorizontal: Bool = false
|
||||
var values: [ColorValue] = []
|
||||
self.queue.sync {
|
||||
widthHeight = self.size
|
||||
isHorizontal = self.horizontal
|
||||
values = self.values
|
||||
}
|
||||
|
||||
guard !values.isEmpty else { return }
|
||||
|
||||
let totalValue = values.reduce(0) { $0 + $1.value }
|
||||
if totalValue < 1 {
|
||||
values.append(ColorValue(1 - totalValue, color: NSColor.lightGray.withAlphaComponent(0.25)))
|
||||
}
|
||||
|
||||
let barSize = widthHeight ?? (isHorizontal ? self.frame.height : self.frame.width)
|
||||
let adjustedTotal = values.reduce(0) { $0 + $1.value }
|
||||
guard adjustedTotal > 0 else { return }
|
||||
|
||||
guard let context = NSGraphicsContext.current?.cgContext else { return }
|
||||
|
||||
let barRect: NSRect = isHorizontal
|
||||
? NSRect(x: 0, y: (self.frame.height - barSize) / 2, width: self.frame.width, height: barSize)
|
||||
: NSRect(x: (self.frame.width - barSize) / 2, y: 0, width: barSize, height: self.frame.height)
|
||||
let clipPath = NSBezierPath(roundedRect: barRect, xRadius: 3, yRadius: 3)
|
||||
|
||||
context.saveGState()
|
||||
clipPath.addClip()
|
||||
|
||||
var list: [(value: Double, path: NSBezierPath)] = []
|
||||
var offset: CGFloat = 0
|
||||
|
||||
for value in values {
|
||||
let color = value.color ?? .controlAccentColor
|
||||
let segmentLength = CGFloat(value.value / adjustedTotal) * (isHorizontal ? self.frame.width : self.frame.height)
|
||||
|
||||
let rect: NSRect = isHorizontal
|
||||
? NSRect(x: offset, y: (self.frame.height - barSize) / 2, width: segmentLength, height: barSize)
|
||||
: NSRect(x: (self.frame.width - barSize) / 2, y: offset, width: barSize, height: segmentLength)
|
||||
|
||||
let path = NSBezierPath(rect: rect)
|
||||
color.setFill()
|
||||
path.fill()
|
||||
|
||||
list.append((value: value.value, path: path))
|
||||
offset += segmentLength
|
||||
}
|
||||
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
public func setValue(_ values: ColorValue) {
|
||||
self.queue.async(flags: .barrier) {
|
||||
self.values = [values]
|
||||
}
|
||||
if self.window?.isVisible ?? false {
|
||||
self.display()
|
||||
}
|
||||
}
|
||||
|
||||
public func setValues(_ values: [ColorValue]) {
|
||||
self.queue.async(flags: .barrier) {
|
||||
self.values = values
|
||||
}
|
||||
if self.window?.isVisible ?? false {
|
||||
self.display()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,10 +279,11 @@ internal class DiskView: NSStackView {
|
||||
public var name: String
|
||||
public var uuid: String
|
||||
private let width: CGFloat
|
||||
private let size: Int64
|
||||
|
||||
private var nameView: NameView
|
||||
private var chartView: ChartView
|
||||
private var barView: BarView
|
||||
private var barView: BarChartView
|
||||
private var legendView: LegendView
|
||||
private var detailsView: DetailsView
|
||||
|
||||
@@ -297,10 +298,16 @@ internal class DiskView: NSStackView {
|
||||
self.uuid = uuid
|
||||
self.name = name
|
||||
self.width = width
|
||||
self.size = size
|
||||
let innerWidth: CGFloat = width - (Constants.Popup.margins * 2)
|
||||
self.nameView = NameView(width: innerWidth, name: name, size: size, free: free, path: path)
|
||||
self.chartView = ChartView(width: innerWidth)
|
||||
self.barView = BarView(width: innerWidth, size: size, free: free)
|
||||
self.barView = BarChartView(frame: NSRect(x: 0, y: 0, width: innerWidth, height: 10), horizontal: true)
|
||||
self.barView.widthAnchor.constraint(equalToConstant: innerWidth).isActive = true
|
||||
self.barView.heightAnchor.constraint(equalToConstant: 10).isActive = true
|
||||
if size != 0 {
|
||||
self.barView.setValue(ColorValue(Double(size - free) / Double(size)))
|
||||
}
|
||||
self.legendView = LegendView(width: innerWidth, id: "\(name)_\(path?.absoluteString ?? "")", size: size, free: free)
|
||||
self.detailsView = DetailsView(width: innerWidth, id: "\(name)_\(path?.absoluteString ?? "")", smart: smart)
|
||||
|
||||
@@ -344,7 +351,9 @@ internal class DiskView: NSStackView {
|
||||
public func update(free: Int64, smart: smart_t?) {
|
||||
self.nameView.update(free: free, read: nil, write: nil)
|
||||
self.legendView.update(free: free)
|
||||
self.barView.update(free: free)
|
||||
if size != 0 {
|
||||
self.barView.setValue(ColorValue(Double(self.size - free) / Double(self.size)))
|
||||
}
|
||||
self.detailsView.update(smart: smart)
|
||||
}
|
||||
|
||||
@@ -560,59 +569,6 @@ internal class ChartView: NSStackView {
|
||||
}
|
||||
}
|
||||
|
||||
internal class BarView: NSView {
|
||||
private let size: Int64
|
||||
private var usedBarSpace: NSView? = nil
|
||||
private var ready: Bool = false
|
||||
|
||||
private var background: NSView? = nil
|
||||
|
||||
public init(width: CGFloat, size: Int64, free: Int64) {
|
||||
self.size = size
|
||||
|
||||
super.init(frame: NSRect(x: 0, y: 0, width: width, height: 10))
|
||||
|
||||
let view: NSView = NSView(frame: NSRect(x: 1, y: 0, width: self.frame.width - 2, height: self.frame.height))
|
||||
view.wantsLayer = true
|
||||
view.layer?.borderColor = NSColor.secondaryLabelColor.cgColor
|
||||
view.layer?.borderWidth = 0.25
|
||||
view.layer?.cornerRadius = 3
|
||||
self.background = view
|
||||
|
||||
let percentage = CGFloat(size - free) / CGFloat(size)
|
||||
let width: CGFloat = (view.frame.width * (percentage < 0 ? 0 : percentage)) / 1
|
||||
self.usedBarSpace = NSView(frame: NSRect(x: 0, y: 0, width: width, height: view.frame.height))
|
||||
self.usedBarSpace?.wantsLayer = true
|
||||
self.usedBarSpace?.layer?.backgroundColor = NSColor.controlAccentColor.cgColor
|
||||
|
||||
view.addSubview(self.usedBarSpace!)
|
||||
self.addSubview(view)
|
||||
|
||||
self.widthAnchor.constraint(equalToConstant: self.frame.width).isActive = true
|
||||
self.heightAnchor.constraint(equalToConstant: self.frame.height).isActive = true
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func updateLayer() {
|
||||
self.background?.layer?.backgroundColor = self.isDarkMode ? NSColor.lightGray.withAlphaComponent(0.1).cgColor : NSColor.white.cgColor
|
||||
}
|
||||
|
||||
public func update(free: Int64?) {
|
||||
if (self.window?.isVisible ?? false) || !self.ready {
|
||||
if let free = free, self.usedBarSpace != nil {
|
||||
let percentage = CGFloat(self.size - free) / CGFloat(self.size)
|
||||
let width: CGFloat = ((self.frame.width - 2) * (percentage < 0 ? 0 : percentage)) / 1
|
||||
self.usedBarSpace?.setFrameSize(NSSize(width: width, height: self.usedBarSpace!.frame.height))
|
||||
}
|
||||
|
||||
self.ready = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class LegendView: NSView {
|
||||
private let size: Int64
|
||||
private var free: Int64
|
||||
|
||||
@@ -485,7 +485,7 @@ internal class FanView: NSStackView {
|
||||
private var modeButtons: ModeButtons? = nil
|
||||
private var debouncer: DispatchWorkItem? = nil
|
||||
|
||||
private var barView: NSView? = nil
|
||||
private var barView: BarChartView? = nil
|
||||
|
||||
private var minBtn: NSButton? = nil
|
||||
private var maxBtn: NSButton? = nil
|
||||
@@ -593,28 +593,18 @@ internal class FanView: NSStackView {
|
||||
valueField.stringValue = self.fanValue == .percentage ? "\(self.fan.percentage)%" : self.fan.formattedValue
|
||||
valueField.toolTip = "\(value)"
|
||||
|
||||
let bar: NSView = NSView(frame: NSRect(x: 0, y: 0, width: 80, height: 8))
|
||||
bar.widthAnchor.constraint(equalToConstant: bar.bounds.width).isActive = true
|
||||
bar.heightAnchor.constraint(equalToConstant: bar.bounds.height).isActive = true
|
||||
bar.wantsLayer = true
|
||||
bar.layer?.backgroundColor = NSColor.textBackgroundColor.cgColor
|
||||
bar.layer?.borderColor = NSColor.quaternaryLabelColor.cgColor
|
||||
bar.layer?.borderWidth = 1
|
||||
bar.layer?.cornerRadius = 2
|
||||
|
||||
let width: CGFloat = (bar.frame.width * CGFloat(self.fan.percentage < 0 ? 0 : self.fan.percentage)) / 100
|
||||
let barInner = NSView(frame: NSRect(x: 0, y: 0, width: width, height: bar.frame.height))
|
||||
barInner.wantsLayer = true
|
||||
barInner.layer?.backgroundColor = NSColor.controlAccentColor.cgColor
|
||||
|
||||
bar.addSubview(barInner)
|
||||
let bar = BarChartView(frame: NSRect(x: 0, y: 0, width: 80, height: 8), horizontal: true)
|
||||
bar.widthAnchor.constraint(equalToConstant: 80).isActive = true
|
||||
bar.heightAnchor.constraint(equalToConstant: 8).isActive = true
|
||||
let percentage = self.fan.percentage < 0 ? 0 : self.fan.percentage
|
||||
bar.setValue(ColorValue(Double(percentage) / 100))
|
||||
|
||||
row.addArrangedSubview(nameField)
|
||||
row.addArrangedSubview(bar)
|
||||
row.addArrangedSubview(valueField)
|
||||
|
||||
self.valueField = valueField
|
||||
self.barView = barInner
|
||||
self.barView = bar
|
||||
|
||||
self.addArrangedSubview(row)
|
||||
}
|
||||
@@ -921,8 +911,8 @@ internal class FanView: NSStackView {
|
||||
self.valueField?.toolTip = value.formattedValue
|
||||
|
||||
if let v = self.barView {
|
||||
let width: CGFloat = (80 * CGFloat(value.percentage < 0 ? 0 : value.percentage)) / 100
|
||||
v.setFrameSize(NSSize(width: width, height: v.frame.height))
|
||||
let percentage = value.percentage < 0 ? 0 : value.percentage
|
||||
v.setValue(ColorValue(Double(percentage) / 100))
|
||||
}
|
||||
|
||||
if self.resetModeAfterSleep && !value.mode.isAutomatic {
|
||||
|
||||
Reference in New Issue
Block a user