From d5d70a299e3b36fb1b895f7fd4016116ed457367 Mon Sep 17 00:00:00 2001 From: Serhiy Mytrovtsiy Date: Mon, 29 Apr 2024 21:56:12 +0200 Subject: [PATCH] feat: added fixed scale option for Network popup (#1787) --- Kit/helpers.swift | 73 +++++++++++++++++++++++++++++++++++++++++ Kit/types.swift | 41 +++++++++++++++++++++++ Modules/Net/popup.swift | 50 ++++++++++++++++++++++++---- 3 files changed, 158 insertions(+), 6 deletions(-) diff --git a/Kit/helpers.swift b/Kit/helpers.swift index 6b5dabff..c636ffdf 100644 --- a/Kit/helpers.swift +++ b/Kit/helpers.swift @@ -1487,3 +1487,76 @@ public func restartApp(_ sender: Any, afterDelay seconds: TimeInterval = 0.5) -> NSApp.terminate(sender) exit(0) } + +public class StepperInput: NSStackView, NSTextFieldDelegate { + public var callback: ((Int) -> Void) = {_ in } + + private let value: NSTextField = NSTextField() + private let stepper: NSStepper = NSStepper() + + private let range: NSRange? + + public init(_ value: Int, range: NSRange? = nil) { + self.range = range + + super.init(frame: .zero) + + self.orientation = .horizontal + self.spacing = 2 + + self.value.font = NSFont.systemFont(ofSize: 12, weight: .regular) + self.value.textColor = .textColor + self.value.isEditable = true + self.value.isSelectable = true + self.value.usesSingleLineMode = true + self.value.maximumNumberOfLines = 1 + self.value.focusRingType = .none + self.value.delegate = self + self.value.stringValue = "\(value)" + + self.stepper.font = NSFont.systemFont(ofSize: 12, weight: .regular) + self.stepper.doubleValue = Double(value)/100 + if let range { + self.stepper.minValue = Double(range.lowerBound)/100 + self.stepper.maxValue = Double(range.upperBound)/100 + } + self.stepper.increment = 0.01 + self.stepper.valueWraps = false + self.stepper.target = self + self.stepper.action = #selector(self.onStepperChange) + + self.addArrangedSubview(self.value) + self.addArrangedSubview(self.stepper) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func controlTextDidChange(_ obj: Notification) { + guard let field = obj.object as? NSTextField else { return } + let filtered = field.stringValue.filter{"0123456789".contains($0)} + if filtered != field.stringValue { + field.stringValue = filtered + } + guard var v = Int(field.stringValue) else { return } + + if let range = self.range { + if v > range.upperBound { + field.stringValue = "\(range.upperBound)" + v = range.upperBound + } else if v < range.lowerBound { + field.stringValue = "\(range.lowerBound)" + v = range.lowerBound + } + } + + self.callback(v) + } + + @objc private func onStepperChange(_ sender: NSStepper) { + let value = Int(sender.doubleValue*100) + self.value.stringValue = "\(value)" + self.callback(value) + } +} diff --git a/Kit/types.swift b/Kit/types.swift index 0b7d9fe3..4ad6b606 100644 --- a/Kit/types.swift +++ b/Kit/types.swift @@ -337,3 +337,44 @@ public var LineChartHistory: [KeyValue_p] = [ KeyValue_t(key: "300", value: "5 minutes"), KeyValue_t(key: "600", value: "10 minutes") ] + +public struct SizeUnit: KeyValue_p, Equatable { + public let key: String + public let value: String + public var additional: Any? + + public static func == (lhs: SizeUnit, rhs: SizeUnit) -> Bool { + return lhs.key == rhs.key + } +} + +extension SizeUnit: CaseIterable { + public static var byte: SizeUnit { return SizeUnit(key: "byte", value: "Bytes") } + public static var KB: SizeUnit { return SizeUnit(key: "KB", value: "KB") } + public static var MB: SizeUnit { return SizeUnit(key: "MB", value: "MB") } + public static var GB: SizeUnit { return SizeUnit(key: "GB", value: "GB") } + public static var TB: SizeUnit { return SizeUnit(key: "TB", value: "TB") } + + public static var allCases: [SizeUnit] { + [.byte, .KB, .MB, .GB, .TB] + } + + public static func fromString(_ key: String, defaultValue: SizeUnit = .byte) -> SizeUnit { + return SizeUnit.allCases.first{ $0.key == key } ?? defaultValue + } + + public func toBytes(_ value: Int) -> Int { + switch self { + case .KB: + return value * 1_024 + case .MB: + return value * 1_024 * 1_024 + case .GB: + return value * 1_024 * 1_024 * 1_024 + case .TB: + return value * 1_024 * 1_024 * 1_024 * 1_024 + default: + return value + } + } +} diff --git a/Modules/Net/popup.swift b/Modules/Net/popup.swift index cc24c232..77154e60 100644 --- a/Modules/Net/popup.swift +++ b/Modules/Net/popup.swift @@ -58,6 +58,9 @@ internal class Popup: PopupWrapper { private var chart: NetworkChartView? = nil private var reverseOrderState: Bool = false private var chartScale: Scale = .none + private var chartFixedScale: Int = 12 + private var chartFixedScaleSize: SizeUnit = .MB + private var chartPrefSection: PreferencesSection? = nil private var connectivityChart: GridChartView? = nil private var processes: ProcessesView? = nil private var sliderView: NSView? = nil @@ -108,6 +111,8 @@ internal class Popup: PopupWrapper { self.uploadColorState = Color.fromString(Store.shared.string(key: "\(self.title)_uploadColor", defaultValue: self.uploadColorState.key)) self.reverseOrderState = Store.shared.bool(key: "\(self.title)_reverseOrder", defaultValue: self.reverseOrderState) self.chartScale = Scale.fromString(Store.shared.string(key: "\(self.title)_chartScale", defaultValue: self.chartScale.key)) + self.chartFixedScale = Store.shared.int(key: "\(self.title)_chartFixedScale", defaultValue: self.chartFixedScale) + self.chartFixedScaleSize = SizeUnit.fromString(Store.shared.string(key: "\(self.title)_chartFixedScaleSize", defaultValue: self.chartFixedScaleSize.key)) self.publicIPState = Store.shared.bool(key: "\(self.title)_publicIP", defaultValue: self.publicIPState) self.spacing = 0 @@ -183,7 +188,9 @@ internal class Popup: PopupWrapper { 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 + num: 120, reversedOrder: self.reverseOrderState, outColor: self.uploadColor, inColor: self.downloadColor, + scale: self.chartScale, + fixedScale: Double(self.chartFixedScaleSize.toBytes(self.chartFixedScale)) ) chart.base = self.base container.addSubview(chart) @@ -523,13 +530,31 @@ internal class Popup: PopupWrapper { )) ])) - view.addArrangedSubview(PreferencesSection([ + self.chartPrefSection = PreferencesSection([ PreferencesRow(localizedString("Main chart scaling"), component: selectView( action: #selector(self.toggleChartScale), - items: Scale.allCases.filter({ $0 != .fixed }), + items: Scale.allCases, selected: self.chartScale.key - )) - ])) + )), + PreferencesRow(localizedString("Scale value"), component: { + let view: NSStackView = NSStackView() + view.orientation = .horizontal + view.spacing = 2 + let valueField = StepperInput(self.chartFixedScale, range: NSRange(location: 1, length: 1023)) + valueField.callback = self.toggleFixedScale + valueField.widthAnchor.constraint(equalToConstant: 80).isActive = true + view.addArrangedSubview(NSView()) + view.addArrangedSubview(valueField) + view.addArrangedSubview(selectView( + action: #selector(self.toggleUploadScaleSize), + items: SizeUnit.allCases, + selected: self.chartFixedScaleSize.key + )) + return view + }()) + ]) + view.addArrangedSubview(self.chartPrefSection!) + self.chartPrefSection?.toggleVisibility(1, newState: self.chartScale == .fixed) view.addArrangedSubview(PreferencesSection([ PreferencesRow(localizedString("Public IP"), component: switchView( @@ -579,7 +604,8 @@ internal class Popup: PopupWrapper { guard let key = sender.representedObject as? String, let value = Scale.allCases.first(where: { $0.key == key }) else { return } self.chartScale = value - self.chart?.setScale(self.chartScale) + self.chart?.setScale(self.chartScale, Double(self.chartFixedScaleSize.toBytes(self.chartFixedScale))) + self.chartPrefSection?.toggleVisibility(1, newState: self.chartScale == .fixed) Store.shared.set(key: "\(self.title)_chartScale", value: key) self.display() } @@ -596,6 +622,18 @@ internal class Popup: PopupWrapper { self.recalculateHeight() }) } + @objc private func toggleFixedScale(_ newValue: Int) { + self.chart?.setScale(self.chartScale, Double(self.chartFixedScaleSize.toBytes(newValue))) + Store.shared.set(key: "\(self.title)_chartFixedScale", value: newValue) + } + @objc private func toggleUploadScaleSize(_ sender: NSMenuItem) { + guard let key = sender.representedObject as? String, + let value = SizeUnit.allCases.first(where: { $0.key == key }) else { return } + self.chartFixedScaleSize = value + self.chart?.setScale(self.chartScale, Double(self.chartFixedScaleSize.toBytes(self.chartFixedScale))) + Store.shared.set(key: "\(self.title)_chartFixedScaleSize", value: key) + self.display() + } // MARK: - helpers