feat: moved network chart to the network chart widget. Replaced NetworkChart with NetworkChartV2.

This commit is contained in:
Serhiy Mytrovtsiy
2024-04-09 18:42:28 +02:00
parent f2d9a491fa
commit 2ec21de043
7 changed files with 123 additions and 251 deletions

View File

@@ -19,10 +19,10 @@ public class NetworkChart: WidgetWrapper {
private var downloadColor: Color = .secondBlue
private var uploadColor: Color = .secondRed
private var scaleState: Scale = .linear
private var commonScaleState: Bool = true
private var reverseOrderState: Bool = false
private var chart: NetworkChartView = NetworkChartView(frame: NSRect(x: 0, y: 0, width: 30, height: Constants.Widget.height-(2*Constants.Widget.margin.y)), num: 60)
private var points: [(Double, Double)] = Array(repeating: (0, 0), count: 60)
private var width: CGFloat {
get {
switch self.historyCount {
@@ -81,16 +81,7 @@ public class NetworkChart: WidgetWrapper {
self.downloadColor = Color.fromString(Store.shared.string(key: "\(self.title)_\(self.type.rawValue)_downloadColor", defaultValue: self.downloadColor.key))
self.uploadColor = Color.fromString(Store.shared.string(key: "\(self.title)_\(self.type.rawValue)_uploadColor", defaultValue: self.uploadColor.key))
self.scaleState = Scale.fromString(Store.shared.string(key: "\(self.title)_\(self.type.rawValue)_scale", defaultValue: self.scaleState.key))
self.commonScaleState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_commonScale", defaultValue: self.commonScaleState)
self.reverseOrderState = Store.shared.bool(key: "\(self.title)_\(self.type.rawValue)_reverseOrder", defaultValue: self.reverseOrderState)
if let downloadColor = self.downloadColor.additional as? NSColor,
let uploadColor = self.uploadColor.additional as? NSColor {
self.chart.setColors(in: downloadColor, out: uploadColor)
}
self.chart.setScale(self.scaleState, self.commonScaleState)
self.chart.reinit(self.historyCount)
self.chart.setReverseOrder(self.reverseOrderState)
}
if preview {
@@ -98,7 +89,7 @@ public class NetworkChart: WidgetWrapper {
for _ in 0..<60 {
list.append((Double.random(in: 0..<23), Double.random(in: 0..<23)))
}
self.chart.points = list
self.points = list
}
let style = NSMutableParagraphStyle()
@@ -166,9 +157,74 @@ public class NetworkChart: WidgetWrapper {
width: box.bounds.width - (offset*2+lineWidth),
height: box.bounds.height - offset
)
self.chart.setFrameSize(NSSize(width: chartFrame.width, height: chartFrame.height))
self.chart.setFrameOrigin(NSPoint(x: chartFrame.origin.x, y: chartFrame.origin.y))
self.chart.draw(chartFrame)
let points = self.points
var topMax: Double = (self.reverseOrderState ? points.map{ $0.0 }.max() : points.map{ $0.1 }.max()) ?? 0
var bottomMax: Double = (self.reverseOrderState ? points.map{ $0.1 }.max() : points.map{ $0.0 }.max()) ?? 0
if topMax == 0 {
topMax = 1
}
if bottomMax == 0 {
bottomMax = 1
}
let zero: CGFloat = (chartFrame.height/2) + chartFrame.origin.y
let xRatio: CGFloat = (chartFrame.width + (lineWidth*3)) / CGFloat(points.count)
let xCenter: CGFloat = chartFrame.height/2 + chartFrame.origin.y
let columnXPoint = { (point: Int) -> CGFloat in
return (CGFloat(point) * xRatio) + (chartFrame.origin.x - lineWidth)
}
let topYPoint = { (point: Int) -> CGFloat in
let value = self.reverseOrderState ? points[point].0 : points[point].1
return scaleValue(scale: self.scaleState, value: value, maxValue: topMax, maxHeight: chartFrame.height/2, limit: 1) + xCenter
}
let bottomYPoint = { (point: Int) -> CGFloat in
let value = self.reverseOrderState ? points[point].1 : points[point].0
return xCenter - scaleValue(scale: self.scaleState, value: value, maxValue: bottomMax, maxHeight: chartFrame.height/2, limit: 1)
}
let topLinePath = NSBezierPath()
topLinePath.move(to: CGPoint(x: columnXPoint(0), y: topYPoint(0)))
let bottomLinePath = NSBezierPath()
bottomLinePath.move(to: CGPoint(x: columnXPoint(0), y: bottomYPoint(0)))
for i in 1..<points.count {
topLinePath.line(to: CGPoint(x: columnXPoint(i), y: topYPoint(i)))
bottomLinePath.line(to: CGPoint(x: columnXPoint(i), y: bottomYPoint(i)))
}
let topColor = (self.reverseOrderState ? self.uploadColor : self.downloadColor).additional as? NSColor
let bottomColor = (self.reverseOrderState ? self.downloadColor : self.uploadColor).additional as? NSColor
bottomColor?.setStroke()
topLinePath.lineWidth = lineWidth
topLinePath.stroke()
topColor?.setStroke()
bottomLinePath.lineWidth = lineWidth
bottomLinePath.stroke()
context.saveGState()
var underLinePath = topLinePath.copy() as! NSBezierPath
underLinePath.line(to: CGPoint(x: columnXPoint(points.count), y: zero))
underLinePath.line(to: CGPoint(x: columnXPoint(0), y: zero))
underLinePath.close()
underLinePath.addClip()
bottomColor?.withAlphaComponent(0.5).setFill()
NSBezierPath(rect: self.frame).fill()
context.restoreGState()
context.saveGState()
underLinePath = bottomLinePath.copy() as! NSBezierPath
underLinePath.line(to: CGPoint(x: columnXPoint(points.count), y: zero))
underLinePath.line(to: CGPoint(x: columnXPoint(0), y: zero))
underLinePath.close()
underLinePath.addClip()
topColor?.withAlphaComponent(0.5).setFill()
NSBezierPath(rect: self.frame).fill()
context.restoreGState()
@@ -182,9 +238,13 @@ public class NetworkChart: WidgetWrapper {
}
public func setValue(upload: Double, download: Double) {
self.points.remove(at: 0)
self.points.append((upload, download))
DispatchQueue.main.async(execute: {
self.chart.addValue(upload: upload, download: download)
self.display()
if self.window?.isVisible ?? false {
self.display()
}
})
}
@@ -241,12 +301,6 @@ public class NetworkChart: WidgetWrapper {
selected: self.scaleState.key
))
view.addArrangedSubview(toggleSettingRow(
title: localizedString("Common scale"),
action: #selector(toggleCommonScale),
state: self.commonScaleState
))
view.addArrangedSubview(toggleSettingRow(
title: localizedString("Reverse order"),
action: #selector(toggleReverseOrder),
@@ -289,11 +343,18 @@ public class NetworkChart: WidgetWrapper {
}
@objc private func toggleHistoryCount(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String, let value = Int(key) else { return }
self.historyCount = value
guard let key = sender.representedObject as? String, let num = Int(key) else { return }
self.historyCount = num
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_historyCount", value: self.historyCount)
if num < self.points.count {
self.points = Array(self.points[self.points.count-num..<self.points.count])
} else {
let origin = self.points
self.points = Array(repeating: (0, 0), count: num)
self.points.replaceSubrange(Range(uncheckedBounds: (lower: origin.count, upper: num)), with: origin)
}
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_historyCount", value: value)
self.chart.reinit(value)
self.display()
}
@@ -303,10 +364,6 @@ public class NetworkChart: WidgetWrapper {
self.downloadColor = newColor
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_downloadColor", value: newColor.key)
}
if let downloadColor = self.downloadColor.additional as? NSColor {
self.chart.setColors(in: downloadColor)
}
self.display()
}
@@ -316,10 +373,6 @@ public class NetworkChart: WidgetWrapper {
self.uploadColor = newColor
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_uploadColor", value: newColor.key)
}
if let uploadColor = self.uploadColor.additional as? NSColor {
self.chart.setColors(out: uploadColor)
}
self.display()
}
@@ -327,21 +380,12 @@ public class NetworkChart: WidgetWrapper {
guard let key = sender.representedObject as? String,
let value = Scale.allCases.first(where: { $0.key == key }) else { return }
self.scaleState = value
self.chart.setScale(value, self.commonScaleState)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_scale", value: key)
self.display()
}
@objc private func toggleCommonScale(_ sender: NSControl) {
self.commonScaleState = controlState(sender)
self.chart.setScale(self.scaleState, self.commonScaleState)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_commonScale", value: self.commonScaleState)
self.display()
}
@objc private func toggleReverseOrder(_ sender: NSControl) {
self.reverseOrderState = controlState(sender)
self.chart.setReverseOrder(self.reverseOrderState)
Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_reverseOrder", value: self.reverseOrderState)
self.display()
}

View File

@@ -134,7 +134,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) {
public init(frame: NSRect, num: Int, suffix: String = "%", color: NSColor = .controlAccentColor, scale: Scale = .none, fixedScale: Double = 1) {
self.points = Array(repeating: nil, count: num)
self.suffix = suffix
self.color = color
@@ -198,7 +198,7 @@ public class LineChartView: NSView {
}
let point = CGPoint(
x: (CGFloat(i) * xRatio) + dirtyRect.origin.x,
x: (CGFloat(i) * xRatio),
y: y
)
line.append(point)
@@ -380,7 +380,7 @@ public class LineChartView: NSView {
}
}
public class NetworkChartViewV2: NSView {
public class NetworkChartView: NSView {
public var id: String = UUID().uuidString
public var base: DataSizeBase = .byte
@@ -465,174 +465,6 @@ public class NetworkChartViewV2: NSView {
}
}
public class NetworkChartView: NSView {
public var id: String = UUID().uuidString
public var base: DataSizeBase = .byte
public var topColor: NSColor
public var bottomColor: NSColor
public var points: [(Double, Double)]
private var scale: Scale = .none
private var fixedScale: Double
private var commonScale: Bool = true
private var reverseOrder: Bool = false
public init(frame: NSRect, num: Int, outColor: NSColor = .systemRed, inColor: NSColor = .systemBlue, scale: Scale = .none, fixedScale: Double = 1) {
self.points = Array(repeating: (0, 0), count: num)
self.topColor = inColor
self.bottomColor = outColor
self.scale = scale
self.fixedScale = fixedScale
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
guard let context = NSGraphicsContext.current?.cgContext else { return }
context.setShouldAntialias(true)
let points = self.points
var topMax: Double = (self.reverseOrder ? points.map{ $0.1 }.max() : points.map{ $0.0 }.max()) ?? 0
var bottomMax: Double = (self.reverseOrder ? points.map{ $0.0 }.max() : points.map{ $0.1 }.max()) ?? 0
if topMax == 0 {
topMax = 1
}
if bottomMax == 0 {
bottomMax = 1
}
if !self.commonScale {
if bottomMax > topMax {
topMax = bottomMax
} else {
bottomMax = topMax
}
}
let lineWidth = 1 / (NSScreen.main?.backingScaleFactor ?? 1)
let zero: CGFloat = (self.frame.height/2) + self.frame.origin.y
let xRatio: CGFloat = (self.frame.width + (lineWidth*3)) / CGFloat(points.count)
let xCenter: CGFloat = self.frame.height/2 + self.frame.origin.y
let columnXPoint = { (point: Int) -> CGFloat in
return (CGFloat(point) * xRatio) + (self.frame.origin.x - lineWidth)
}
let topYPoint = { (point: Int) -> CGFloat in
let value = self.reverseOrder ? points[point].1 : points[point].0
return scaleValue(scale: self.scale, value: value, maxValue: topMax, maxHeight: self.frame.height/2, limit: self.fixedScale) + xCenter
}
let bottomYPoint = { (point: Int) -> CGFloat in
let value = self.reverseOrder ? points[point].0 : points[point].1
return xCenter - scaleValue(scale: self.scale, value: value, maxValue: bottomMax, maxHeight: self.frame.height/2, limit: self.fixedScale)
}
let topLinePath = NSBezierPath()
topLinePath.move(to: CGPoint(x: columnXPoint(0), y: topYPoint(0)))
let bottomLinePath = NSBezierPath()
bottomLinePath.move(to: CGPoint(x: columnXPoint(0), y: bottomYPoint(0)))
for i in 1..<points.count {
topLinePath.line(to: CGPoint(x: columnXPoint(i), y: topYPoint(i)))
bottomLinePath.line(to: CGPoint(x: columnXPoint(i), y: bottomYPoint(i)))
}
let topColor = self.reverseOrder ? self.bottomColor : self.topColor
let bottomColor = self.reverseOrder ? self.topColor : self.bottomColor
bottomColor.setStroke()
topLinePath.lineWidth = lineWidth
topLinePath.stroke()
topColor.setStroke()
bottomLinePath.lineWidth = lineWidth
bottomLinePath.stroke()
context.saveGState()
var underLinePath = topLinePath.copy() as! NSBezierPath
underLinePath.line(to: CGPoint(x: columnXPoint(points.count), y: zero))
underLinePath.line(to: CGPoint(x: columnXPoint(0), y: zero))
underLinePath.close()
underLinePath.addClip()
bottomColor.withAlphaComponent(0.5).setFill()
NSBezierPath(rect: self.frame).fill()
context.restoreGState()
context.saveGState()
underLinePath = bottomLinePath.copy() as! NSBezierPath
underLinePath.line(to: CGPoint(x: columnXPoint(points.count), y: zero))
underLinePath.line(to: CGPoint(x: columnXPoint(0), y: zero))
underLinePath.close()
underLinePath.addClip()
topColor.withAlphaComponent(0.5).setFill()
NSBezierPath(rect: self.frame).fill()
}
public func addValue(upload: Double, download: Double) {
self.points.remove(at: 0)
self.points.append((upload, download))
if self.window?.isVisible ?? false {
self.display()
}
}
public func reinit(_ num: Int = 60) {
guard self.points.count != num else { return }
if num < self.points.count {
self.points = Array(self.points[self.points.count-num..<self.points.count])
} else {
let origin = self.points
self.points = Array(repeating: (0, 0), count: num)
self.points.replaceSubrange(Range(uncheckedBounds: (lower: origin.count, upper: num)), with: origin)
}
}
public func setScale(_ newScale: Scale, _ commonScale: Bool = false, _ fixedScale: Double = 1) {
self.scale = newScale
self.fixedScale = fixedScale
self.commonScale = commonScale
if self.window?.isVisible ?? false {
self.display()
}
}
public func setReverseOrder(_ newValue: Bool) {
self.reverseOrder = newValue
if self.window?.isVisible ?? false {
self.display()
}
}
public func setColors(in inColor: NSColor? = nil, out outColor: NSColor? = nil) {
var needUpdate: Bool = false
if let newColor = inColor, self.topColor != newColor {
self.topColor = newColor
needUpdate = true
}
if let newColor = outColor, self.bottomColor != newColor {
self.bottomColor = newColor
needUpdate = true
}
if needUpdate && self.window?.isVisible ?? false {
self.display()
}
}
}
public class PieChartView: NSView {
public var id: String = UUID().uuidString

View File

@@ -420,7 +420,7 @@ internal class NameView: NSStackView {
}
internal class ChartView: NSStackView {
private var chart: NetworkChartViewV2? = nil
private var chart: NetworkChartView? = nil
private var ready: Bool = false
private var readColor: NSColor {
@@ -436,7 +436,7 @@ internal class ChartView: NSStackView {
self.wantsLayer = true
self.layer?.cornerRadius = 3
let chart = NetworkChartViewV2(frame: NSRect(
let chart = NetworkChartView(frame: NSRect(
x: 0,
y: 1,
width: self.frame.width,

View File

@@ -14,7 +14,7 @@ import Kit
public class Portal: PortalWrapper {
private var circle: PieChartView? = nil
private var chart: NetworkChartViewV2? = nil
private var chart: NetworkChartView? = nil
private var nameField: NSTextField? = nil
private var usedField: NSTextField? = nil
@@ -91,7 +91,7 @@ public class Portal: PortalWrapper {
self.usedField = portalRow(view, title: "\(localizedString("Used")):")
self.freeField = portalRow(view, title: "\(localizedString("Free")):")
let chart = NetworkChartViewV2(frame: NSRect.zero, num: 120, minMax: false, outColor: self.writeColor, inColor: self.readColor)
let chart = NetworkChartView(frame: NSRect.zero, num: 120, minMax: false, outColor: self.writeColor, inColor: self.readColor)
chart.heightAnchor.constraint(equalToConstant: 26).isActive = true
self.chart = chart
view.addArrangedSubview(chart)

View File

@@ -55,7 +55,8 @@ internal class Popup: PopupWrapper {
private var processesInitialized: Bool = false
private var connectionInitialized: Bool = false
private var chart: NetworkChartViewV2? = nil
private var chart: NetworkChartView? = nil
private var reverseOrderState: Bool = false
private var chartScale: Scale = .none
private var connectivityChart: GridChartView? = nil
private var processes: ProcessesView? = nil
@@ -89,7 +90,6 @@ internal class Popup: PopupWrapper {
}
return value
}
private var reverseOrderState: Bool = false
private var latency: [Double] = []
@@ -175,7 +175,7 @@ internal class Popup: PopupWrapper {
container.layer?.backgroundColor = NSColor.lightGray.withAlphaComponent(0.1).cgColor
container.layer?.cornerRadius = 3
let chart = NetworkChartViewV2(
let chart = NetworkChartView(
frame: NSRect(x: 0, y: 1, width: container.frame.width, height: container.frame.height - 2),
num: 120, reversedOrder: self.reverseOrderState, outColor: self.uploadColor, inColor: self.downloadColor, scale: self.chartScale
)

View File

@@ -13,26 +13,32 @@ import Cocoa
import Kit
public class Portal: PortalWrapper {
private var chart: NetworkChartViewV2? = nil
private var chart: NetworkChartView? = nil
private var publicIPField: NSTextField? = nil
private var base: DataSizeBase {
DataSizeBase(rawValue: Store.shared.string(key: "\(self.name)_base", defaultValue: "byte")) ?? .byte
}
private var reverseOrderState: Bool {
Store.shared.bool(key: "\(self.name)_reverseOrder", defaultValue: false)
}
private var chartScale: Scale {
Scale.fromString(Store.shared.string(key: "\(self.name)_chartScale", defaultValue: Scale.none.key))
}
private var downloadColorState: Color = .secondBlue
private var downloadColor: NSColor {
let v = Color.fromString(Store.shared.string(key: "\(self.name)_downloadColor", defaultValue: Color.secondBlue.key))
var value = NSColor.systemBlue
if let color = self.downloadColorState.additional as? NSColor {
if let color = v.additional as? NSColor {
value = color
}
return value
}
private var uploadColorState: Color = .secondRed
private var uploadColor: NSColor {
let v = Color.fromString(Store.shared.string(key: "\(self.name)_uploadColor", defaultValue: Color.secondRed.key))
var value = NSColor.systemRed
if let color = self.uploadColorState.additional as? NSColor {
if let color = v.additional as? NSColor {
value = color
}
return value
@@ -41,11 +47,9 @@ public class Portal: PortalWrapper {
private var initialized: Bool = false
public override func load() {
self.loadColors()
let view = NSStackView()
view.orientation = .vertical
view.distribution = .fillEqually
view.distribution = .fill
view.spacing = Constants.Popup.spacing*2
view.edgeInsets = NSEdgeInsets(
top: 0,
@@ -54,8 +58,16 @@ public class Portal: PortalWrapper {
right: Constants.Popup.spacing*2
)
let chartView = self.chartView()
view.addArrangedSubview(chartView)
let chart = NetworkChartView(
frame: CGRect(x: 0, y: 0, width: self.frame.width - (Constants.Popup.spacing*8), height: 68),
num: 120,
reversedOrder: self.reverseOrderState,
outColor: self.uploadColor,
inColor: self.downloadColor,
scale: self.chartScale
)
self.chart = chart
view.addArrangedSubview(chart)
self.publicIPField = portalRow(view, title: "\(localizedString("Public IP")):", value: localizedString("Unknown"))
view.subviews.last?.heightAnchor.constraint(equalToConstant: 16).isActive = true
@@ -63,24 +75,6 @@ public class Portal: PortalWrapper {
self.addArrangedSubview(view)
}
public func loadColors() {
self.downloadColorState = Color.fromString(Store.shared.string(key: "\(self.name)_downloadColor", defaultValue: self.downloadColorState.key))
self.uploadColorState = Color.fromString(Store.shared.string(key: "\(self.name)_uploadColor", defaultValue: self.uploadColorState.key))
}
private func chartView() -> NSView {
let view = NSStackView()
view.orientation = .vertical
view.distribution = .fill
view.spacing = Constants.Popup.spacing*2
let chart = NetworkChartViewV2(frame: NSRect.zero, num: 120, outColor: self.uploadColor, inColor: self.downloadColor)
self.chart = chart
view.addArrangedSubview(chart)
return view
}
public func usageCallback(_ value: Network_Usage) {
DispatchQueue.main.async(execute: {
if let chart = self.chart {
@@ -88,6 +82,8 @@ public class Portal: PortalWrapper {
chart.base = self.base
}
chart.addValue(upload: Double(value.bandwidth.upload), download: Double(value.bandwidth.download))
chart.setScale(self.chartScale, 1)
chart.setColors(in: self.downloadColor, out: self.uploadColor)
}
if let view = self.publicIPField, view.stringValue != value.raddr.v4 {

View File

@@ -149,7 +149,7 @@ class SettingsWindow: NSWindow, NSWindowDelegate, NSToolbarDelegate {
return nil
}
}
func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [.flexibleSpace, .toggleButton]
}