diff --git a/Kit/extensions.swift b/Kit/extensions.swift index bdfa816c..ff82a6b9 100644 --- a/Kit/extensions.swift +++ b/Kit/extensions.swift @@ -451,6 +451,38 @@ public extension NSView { return select } + + func switchView(action: Selector, state: Bool) -> NSSwitch { + let s = NSSwitch() + s.controlSize = .mini + s.state = state ? .on : .off + s.action = action + s.target = self + return s + } + + func buttonIconView(_ action: Selector, icon: NSImage) -> NSButton { + let button = NSButton() + button.heightAnchor.constraint(equalToConstant: 22).isActive = true + button.bezelStyle = .regularSquare + button.translatesAutoresizingMaskIntoConstraints = false + button.imageScaling = .scaleNone + button.image = icon + button.contentTintColor = .labelColor + button.isBordered = false + button.action = action + button.target = self + button.focusRingType = .none + return button + } + + func textView(_ value: String) -> NSTextField { + let field: NSTextField = TextView() + field.font = NSFont.systemFont(ofSize: 13, weight: .regular) + field.stringValue = value + field.isSelectable = true + return field + } } public class NSButtonWithPadding: NSButton { @@ -466,7 +498,7 @@ public class NSButtonWithPadding: NSButton { } public class TextView: NSTextField { - public override init(frame: NSRect) { + public override init(frame: NSRect = .zero) { super.init(frame: frame) self.isEditable = false diff --git a/Kit/helpers.swift b/Kit/helpers.swift index 933eb517..df151549 100644 --- a/Kit/helpers.swift +++ b/Kit/helpers.swift @@ -8,6 +8,7 @@ // // Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. // +// swiftlint:disable file_length import Cocoa import ServiceManagement @@ -1348,3 +1349,95 @@ var isDarkMode: Bool { return false } } + +public class PreferencesSection: NSStackView { + public init(_ components: [PreferencesRow] = []) { + super.init(frame: .zero) + + self.orientation = .vertical + + self.wantsLayer = true + self.layer?.cornerRadius = 5 + self.layer?.borderColor = NSColor.separatorColor.withAlphaComponent(0.05).cgColor + self.layer?.borderWidth = 1 + self.layer?.backgroundColor = NSColor.quaternaryLabelColor.withAlphaComponent(0.025).cgColor + + self.edgeInsets = NSEdgeInsets( + top: Constants.Settings.margin/2, + left: Constants.Settings.margin, + bottom: Constants.Settings.margin/2, + right: Constants.Settings.margin + ) + self.spacing = Constants.Settings.margin/2 + + for item in components { + self.add(item) + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func updateLayer() { + self.layer?.borderColor = NSColor.separatorColor.withAlphaComponent(0.05).cgColor + self.layer?.backgroundColor = NSColor.quaternaryLabelColor.withAlphaComponent(0.025).cgColor + } + + public func add(_ view: NSView) { + if !self.subviews.isEmpty { + self.addArrangedSubview(PreferencesSeparator()) + } + self.addArrangedSubview(view) + } + + public func toggleVisibility(_ at: Int, newState: Bool) { + for i in self.subviews.indices where i/2 == at && Double(i).remainder(dividingBy: 2) == 0 { + self.subviews[i-1].isHidden = !newState + self.subviews[i].isHidden = !newState + } + } +} + +public class PreferencesSeparator: NSView { + public init() { + super.init(frame: .zero) + self.wantsLayer = true + self.layer?.backgroundColor = NSColor.separatorColor.withAlphaComponent(0.05).cgColor + self.heightAnchor.constraint(equalToConstant: 1).isActive = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func updateLayer() { + self.layer?.backgroundColor = NSColor.separatorColor.withAlphaComponent(0.05).cgColor + } +} + +public class PreferencesRow: NSStackView { + public init(_ title: String? = nil, component: NSView) { + super.init(frame: .zero) + + self.orientation = .horizontal + self.distribution = .fill + self.alignment = .top + self.edgeInsets = NSEdgeInsets(top: Constants.Settings.margin/2, left: 0, bottom: Constants.Settings.margin/2, right: 0) + self.spacing = 0 + + let field: NSTextField = TextView() + field.font = NSFont.systemFont(ofSize: 13, weight: .regular) + if let title { + field.stringValue = title + self.addArrangedSubview(field) + } + + self.addArrangedSubview(NSView()) + self.addArrangedSubview(component) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Stats/Supporting Files/Assets.xcassets/cancel.imageset/Contents.json b/Stats/Supporting Files/Assets.xcassets/cancel.imageset/Contents.json new file mode 100644 index 00000000..14c95d8c --- /dev/null +++ b/Stats/Supporting Files/Assets.xcassets/cancel.imageset/Contents.json @@ -0,0 +1,27 @@ +{ + "images": [ + { + "filename": "baseline_cancel_black_24pt_1x.png", + "idiom": "universal", + "scale": "1x", + "size": "24x24" + }, + { + "filename": "baseline_cancel_black_24pt_2x.png", + "idiom": "universal", + "scale": "2x", + "size": "24x24" + }, + { + "filename": "baseline_cancel_black_24pt_3x.png", + "idiom": "universal", + "scale": "3x", + "size": "24x24" + } + ], + "info": { + "author": "xcode", + "template-rendering-intent": "template", + "version": 1 + } +} \ No newline at end of file diff --git a/Stats/Supporting Files/Assets.xcassets/cancel.imageset/baseline_cancel_black_24pt_1x.png b/Stats/Supporting Files/Assets.xcassets/cancel.imageset/baseline_cancel_black_24pt_1x.png new file mode 100644 index 00000000..f0176921 Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/cancel.imageset/baseline_cancel_black_24pt_1x.png differ diff --git a/Stats/Supporting Files/Assets.xcassets/cancel.imageset/baseline_cancel_black_24pt_2x.png b/Stats/Supporting Files/Assets.xcassets/cancel.imageset/baseline_cancel_black_24pt_2x.png new file mode 100644 index 00000000..9f7ca9b3 Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/cancel.imageset/baseline_cancel_black_24pt_2x.png differ diff --git a/Stats/Supporting Files/Assets.xcassets/cancel.imageset/baseline_cancel_black_24pt_3x.png b/Stats/Supporting Files/Assets.xcassets/cancel.imageset/baseline_cancel_black_24pt_3x.png new file mode 100644 index 00000000..8b599e52 Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/cancel.imageset/baseline_cancel_black_24pt_3x.png differ diff --git a/Stats/Supporting Files/Assets.xcassets/export.imageset/Contents.json b/Stats/Supporting Files/Assets.xcassets/export.imageset/Contents.json new file mode 100644 index 00000000..c438d6ea --- /dev/null +++ b/Stats/Supporting Files/Assets.xcassets/export.imageset/Contents.json @@ -0,0 +1,27 @@ +{ + "images": [ + { + "filename": "baseline_ios_share_black_24pt_1x.png", + "idiom": "universal", + "scale": "1x", + "size": "24x24" + }, + { + "filename": "baseline_ios_share_black_24pt_2x.png", + "idiom": "universal", + "scale": "2x", + "size": "24x24" + }, + { + "filename": "baseline_ios_share_black_24pt_3x.png", + "idiom": "universal", + "scale": "3x", + "size": "24x24" + } + ], + "info": { + "author": "xcode", + "template-rendering-intent": "template", + "version": 1 + } +} \ No newline at end of file diff --git a/Stats/Supporting Files/Assets.xcassets/export.imageset/baseline_ios_share_black_24pt_1x.png b/Stats/Supporting Files/Assets.xcassets/export.imageset/baseline_ios_share_black_24pt_1x.png new file mode 100644 index 00000000..7f7ed2df Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/export.imageset/baseline_ios_share_black_24pt_1x.png differ diff --git a/Stats/Supporting Files/Assets.xcassets/export.imageset/baseline_ios_share_black_24pt_2x.png b/Stats/Supporting Files/Assets.xcassets/export.imageset/baseline_ios_share_black_24pt_2x.png new file mode 100644 index 00000000..9531a62b Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/export.imageset/baseline_ios_share_black_24pt_2x.png differ diff --git a/Stats/Supporting Files/Assets.xcassets/export.imageset/baseline_ios_share_black_24pt_3x.png b/Stats/Supporting Files/Assets.xcassets/export.imageset/baseline_ios_share_black_24pt_3x.png new file mode 100644 index 00000000..b1043806 Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/export.imageset/baseline_ios_share_black_24pt_3x.png differ diff --git a/Stats/Supporting Files/Assets.xcassets/import.imageset/Contents.json b/Stats/Supporting Files/Assets.xcassets/import.imageset/Contents.json new file mode 100644 index 00000000..a3ea193d --- /dev/null +++ b/Stats/Supporting Files/Assets.xcassets/import.imageset/Contents.json @@ -0,0 +1,27 @@ +{ + "images": [ + { + "filename": "baseline_download_black_24pt_1x.png", + "idiom": "universal", + "scale": "1x", + "size": "24x24" + }, + { + "filename": "baseline_download_black_24pt_2x.png", + "idiom": "universal", + "scale": "2x", + "size": "24x24" + }, + { + "filename": "baseline_download_black_24pt_3x.png", + "idiom": "universal", + "scale": "3x", + "size": "24x24" + } + ], + "info": { + "author": "xcode", + "template-rendering-intent": "template", + "version": 1 + } +} \ No newline at end of file diff --git a/Stats/Supporting Files/Assets.xcassets/import.imageset/baseline_download_black_24pt_1x.png b/Stats/Supporting Files/Assets.xcassets/import.imageset/baseline_download_black_24pt_1x.png new file mode 100644 index 00000000..733008d2 Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/import.imageset/baseline_download_black_24pt_1x.png differ diff --git a/Stats/Supporting Files/Assets.xcassets/import.imageset/baseline_download_black_24pt_2x.png b/Stats/Supporting Files/Assets.xcassets/import.imageset/baseline_download_black_24pt_2x.png new file mode 100644 index 00000000..19d7fc63 Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/import.imageset/baseline_download_black_24pt_2x.png differ diff --git a/Stats/Supporting Files/Assets.xcassets/import.imageset/baseline_download_black_24pt_3x.png b/Stats/Supporting Files/Assets.xcassets/import.imageset/baseline_download_black_24pt_3x.png new file mode 100644 index 00000000..3aefc3b2 Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/import.imageset/baseline_download_black_24pt_3x.png differ diff --git a/Stats/Supporting Files/Assets.xcassets/trash.imageset/Contents.json b/Stats/Supporting Files/Assets.xcassets/trash.imageset/Contents.json new file mode 100644 index 00000000..dd45b3a1 --- /dev/null +++ b/Stats/Supporting Files/Assets.xcassets/trash.imageset/Contents.json @@ -0,0 +1,27 @@ +{ + "images": [ + { + "filename": "baseline_delete_black_24pt_1x.png", + "idiom": "universal", + "scale": "1x", + "size": "24x24" + }, + { + "filename": "baseline_delete_black_24pt_2x.png", + "idiom": "universal", + "scale": "2x", + "size": "24x24" + }, + { + "filename": "baseline_delete_black_24pt_3x.png", + "idiom": "universal", + "scale": "3x", + "size": "24x24" + } + ], + "info": { + "author": "xcode", + "template-rendering-intent": "template", + "version": 1 + } +} \ No newline at end of file diff --git a/Stats/Supporting Files/Assets.xcassets/trash.imageset/baseline_delete_black_24pt_1x.png b/Stats/Supporting Files/Assets.xcassets/trash.imageset/baseline_delete_black_24pt_1x.png new file mode 100644 index 00000000..66d7e7ac Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/trash.imageset/baseline_delete_black_24pt_1x.png differ diff --git a/Stats/Supporting Files/Assets.xcassets/trash.imageset/baseline_delete_black_24pt_2x.png b/Stats/Supporting Files/Assets.xcassets/trash.imageset/baseline_delete_black_24pt_2x.png new file mode 100644 index 00000000..9e5bc7a9 Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/trash.imageset/baseline_delete_black_24pt_2x.png differ diff --git a/Stats/Supporting Files/Assets.xcassets/trash.imageset/baseline_delete_black_24pt_3x.png b/Stats/Supporting Files/Assets.xcassets/trash.imageset/baseline_delete_black_24pt_3x.png new file mode 100644 index 00000000..37703861 Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/trash.imageset/baseline_delete_black_24pt_3x.png differ diff --git a/Stats/Views/AppSettings.swift b/Stats/Views/AppSettings.swift index 7bc64231..b40c649e 100644 --- a/Stats/Views/AppSettings.swift +++ b/Stats/Views/AppSettings.swift @@ -35,58 +35,135 @@ class ApplicationSettings: NSStackView { set { Store.shared.set(key: "CombinedModules_popup", value: newValue) } } + private var importIcon: NSImage { + if #available(macOS 12.0, *), let icon = iconFromSymbol(name: "square.and.arrow.down", scale: .large) { + return icon + } + return NSImage(named: NSImage.Name("import"))! + } + private var exportIcon: NSImage { + if #available(macOS 12.0, *), let icon = iconFromSymbol(name: "square.and.arrow.up", scale: .large) { + return icon + } + return NSImage(named: NSImage.Name("export"))! + } + private var resetIcon: NSImage { + if #available(macOS 12.0, *), let icon = iconFromSymbol(name: "trash", scale: .large) { + return icon + } + return NSImage(named: NSImage.Name("trash"))! + } + private var uninstallIcon: NSImage { + if #available(macOS 12.0, *), let icon = iconFromSymbol(name: "xmark.circle", scale: .large) { + return icon + } + return NSImage(named: NSImage.Name("cancel"))! + } + + private var updateSelector: NSPopUpButton? + private var startAtLoginBtn: NSSwitch? + private var telemetryBtn: NSSwitch? + + private var combinedModulesView: PreferencesSection? + private var fanHelperView: PreferencesSection? + private let updateWindow: UpdateWindow = UpdateWindow() private let moduleSelector: ModuleSelectorView = ModuleSelectorView() - private var updateSelector: NSPopUpButton? - private var startAtLoginBtn: NSButton? - private var uninstallHelperButton: NSButton? - private var buttonsContainer: NSStackView? - private var telemetryBtn: NSButton? - - private var combinedModules: NSView? - private var combinedModulesSeparator: NSView? - - private var buttons: NSView? - private var buttonsSeparator: NSView? init() { super.init(frame: NSRect(x: 0, y: 0, width: Constants.Settings.width, height: Constants.Settings.height)) - self.translatesAutoresizingMaskIntoConstraints = false - let scrollView = ScrollableStackView() - scrollView.stackView.spacing = 0 - - let settings = self.settingsView() - let appSettings = self.appSettingsView() + let scrollView = ScrollableStackView(orientation: .vertical) + scrollView.stackView.edgeInsets = NSEdgeInsets( + top: Constants.Settings.margin, + left: Constants.Settings.margin, + bottom: Constants.Settings.margin, + right: Constants.Settings.margin + ) + scrollView.stackView.spacing = Constants.Settings.margin scrollView.stackView.addArrangedSubview(self.informationView()) - scrollView.stackView.addArrangedSubview(self.separatorView()) - scrollView.stackView.addArrangedSubview(settings) - scrollView.stackView.addArrangedSubview(self.separatorView()) - scrollView.stackView.addArrangedSubview(self.combinedModulesView()) - var separator = self.separatorView() - self.combinedModulesSeparator = separator - scrollView.stackView.addArrangedSubview(separator) - scrollView.stackView.addArrangedSubview(appSettings) - separator = self.separatorView() - self.buttonsSeparator = separator - scrollView.stackView.addArrangedSubview(separator) - scrollView.stackView.addArrangedSubview(self.buttonsView()) - self.toggleCombinedModulesView() - self.toggleButtonsView() + self.updateSelector = selectView( + action: #selector(self.toggleUpdateInterval), + items: AppUpdateIntervals, + selected: self.updateIntervalValue + ) + self.startAtLoginBtn = switchView( + action: #selector(self.toggleLaunchAtLogin), + state: LaunchAtLogin.isEnabled + ) + self.telemetryBtn = switchView( + action: #selector(self.toggleTelemetry), + state: telemetry.isEnabled + ) + + scrollView.stackView.addArrangedSubview(PreferencesSection([ + PreferencesRow(localizedString("Check for updates"), component: self.updateSelector!), + PreferencesRow(localizedString("Temperature"), component: selectView( + action: #selector(self.toggleTemperatureUnits), + items: TemperatureUnits, + selected: self.temperatureUnitsValue + )), + PreferencesRow(localizedString("Show icon in dock"), component: switchView( + action: #selector(self.toggleDock), + state: Store.shared.bool(key: "dockIcon", defaultValue: false) + )), + PreferencesRow(localizedString("Start at login"), component: self.startAtLoginBtn!), + PreferencesRow(localizedString("Share anonymous telemetry"), component: self.telemetryBtn!) + ])) + + self.combinedModulesView = PreferencesSection([ + PreferencesRow(localizedString("Combined modules"), component: switchView( + action: #selector(self.toggleCombinedModules), + state: self.combinedModulesState + )), + PreferencesRow(component: self.moduleSelector), + PreferencesRow(localizedString("Spacing"), component: selectView( + action: #selector(self.toggleCombinedModulesSpacing), + items: CombinedModulesSpacings, + selected: self.combinedModulesSpacing + )), + PreferencesRow(localizedString("Combined details"), component: switchView( + action: #selector(self.toggleCombinedModulesPopup), + state: self.combinedModulesPopup + )) + ]) + scrollView.stackView.addArrangedSubview(self.combinedModulesView!) + self.combinedModulesView?.toggleVisibility(1, newState: self.combinedModulesState) + self.combinedModulesView?.toggleVisibility(2, newState: self.combinedModulesState) + self.combinedModulesView?.toggleVisibility(3, newState: self.combinedModulesState) + + scrollView.stackView.addArrangedSubview(PreferencesSection([ + PreferencesRow( + localizedString("Import settings"), + component: buttonIconView(#selector(self.importSettings), icon: self.importIcon) + ), + PreferencesRow( + localizedString("Export settings"), + component: buttonIconView(#selector(self.exportSettings), icon: self.exportIcon) + ), + PreferencesRow( + localizedString("Reset settings"), + component: buttonIconView(#selector(self.resetSettings), icon: self.resetIcon) + ) + ])) + + self.fanHelperView = PreferencesSection([ + PreferencesRow( + localizedString("Uninstall fan helper"), + component: buttonIconView(#selector(self.uninstallHelper), icon: self.uninstallIcon) + ) + ]) + scrollView.stackView.addArrangedSubview(self.fanHelperView!) self.addArrangedSubview(scrollView) - if let settingsGrid = settings.subviews.first { - appSettings.subviews.first?.widthAnchor.constraint(equalTo: settingsGrid.widthAnchor).isActive = true - } - NotificationCenter.default.addObserver(self, selector: #selector(toggleUninstallHelperButton), name: .fanHelperState, object: nil) } - required public init?(coder: NSCoder) { + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -111,7 +188,7 @@ class ApplicationSettings: NSStackView { private func informationView() -> NSView { let view = NSStackView() - view.heightAnchor.constraint(equalToConstant: 240).isActive = true + view.heightAnchor.constraint(equalToConstant: 220).isActive = true view.orientation = .vertical view.distribution = .fill view.alignment = .centerY @@ -161,241 +238,6 @@ class ApplicationSettings: NSStackView { return view } - private func settingsView() -> NSView { - let view: NSStackView = NSStackView() - view.orientation = .vertical - view.edgeInsets = NSEdgeInsets( - top: Constants.Settings.margin, - left: Constants.Settings.margin, - bottom: Constants.Settings.margin, - right: Constants.Settings.margin - ) - view.spacing = 10 - view.translatesAutoresizingMaskIntoConstraints = false - view.widthAnchor.constraint(equalToConstant: self.frame.width - 15).isActive = true - - let grid: NSGridView = NSGridView(frame: NSRect(x: 0, y: 0, width: view.frame.width, height: 0)) - grid.rowSpacing = 10 - grid.columnSpacing = 20 - grid.xPlacement = .trailing - grid.rowAlignment = .firstBaseline - grid.translatesAutoresizingMaskIntoConstraints = false - grid.setContentHuggingPriority(.defaultHigh, for: .horizontal) - grid.setContentHuggingPriority(.defaultHigh, for: .vertical) - - self.updateSelector = selectView( - action: #selector(self.toggleUpdateInterval), - items: AppUpdateIntervals, - selected: self.updateIntervalValue - ) - grid.addRow(with: [ - self.titleView(localizedString("Check for updates")), - self.updateSelector! - ]) - grid.addRow(with: [ - self.titleView(localizedString("Temperature")), - selectView( - action: #selector(self.toggleTemperatureUnits), - items: TemperatureUnits, - selected: self.temperatureUnitsValue - ) - ]) - grid.addRow(with: [NSGridCell.emptyContentView, self.toggleView( - action: #selector(self.toggleDock), - state: Store.shared.bool(key: "dockIcon", defaultValue: false), - text: localizedString("Show icon in dock") - )]) - self.startAtLoginBtn = self.toggleView( - action: #selector(self.toggleLaunchAtLogin), - state: LaunchAtLogin.isEnabled, - text: localizedString("Start at login") - ) - grid.addRow(with: [NSGridCell.emptyContentView, self.startAtLoginBtn!]) - - self.telemetryBtn = self.toggleView( - action: #selector(self.toggleTelemetry), - state: telemetry.isEnabled, - text: localizedString("Share anonymous telemetry") - ) - grid.addRow(with: [NSGridCell.emptyContentView, self.telemetryBtn!]) - - grid.addRow(with: [NSGridCell.emptyContentView, self.toggleView( - action: #selector(self.toggleCombinedModules), - state: self.combinedModulesState, - text: localizedString("Combined modules") - )]) - - view.addArrangedSubview(grid) - - return view - } - - private func combinedModulesView() -> NSView { - let view: NSStackView = NSStackView() - view.orientation = .vertical - view.edgeInsets = NSEdgeInsets( - top: Constants.Settings.margin, - left: Constants.Settings.margin, - bottom: Constants.Settings.margin, - right: Constants.Settings.margin - ) - view.spacing = 10 - view.translatesAutoresizingMaskIntoConstraints = false - view.widthAnchor.constraint(equalToConstant: self.frame.width - 15).isActive = true - - let grid: NSGridView = NSGridView(frame: NSRect(x: 0, y: 0, width: view.frame.width, height: 0)) - grid.rowSpacing = 10 - grid.columnSpacing = 20 - grid.xPlacement = .trailing - grid.rowAlignment = .firstBaseline - grid.translatesAutoresizingMaskIntoConstraints = false - grid.setContentHuggingPriority(.defaultHigh, for: .horizontal) - grid.setContentHuggingPriority(.defaultHigh, for: .vertical) - - grid.addRow(with: [ - self.titleView(localizedString("Spacing")), - selectView( - action: #selector(self.toggleCombinedModulesSpacing), - items: CombinedModulesSpacings, - selected: self.combinedModulesSpacing - ) - ]) - grid.addRow(with: [NSGridCell.emptyContentView, self.toggleView( - action: #selector(self.toggleCombinedModulesPopup), - state: self.combinedModulesPopup, - text: localizedString("Combined details") - )]) - - view.addArrangedSubview(self.moduleSelector) - view.addArrangedSubview(grid) - - self.combinedModules = view - return view - } - - private func appSettingsView() -> NSView { - let view: NSStackView = NSStackView() - view.orientation = .vertical - view.edgeInsets = NSEdgeInsets( - top: Constants.Settings.margin, - left: Constants.Settings.margin, - bottom: Constants.Settings.margin, - right: Constants.Settings.margin - ) - view.spacing = 10 - view.translatesAutoresizingMaskIntoConstraints = false - view.widthAnchor.constraint(equalToConstant: self.frame.width - 15).isActive = true - - let grid: NSGridView = NSGridView(frame: NSRect(x: 0, y: 0, width: view.frame.width, height: 0)) - grid.rowSpacing = 10 - grid.columnSpacing = 20 - grid.xPlacement = .trailing - grid.rowAlignment = .firstBaseline - grid.translatesAutoresizingMaskIntoConstraints = false - grid.setContentHuggingPriority(.defaultHigh, for: .horizontal) - grid.setContentHuggingPriority(.defaultHigh, for: .vertical) - - let importBtn: NSButton = NSButton() - importBtn.title = localizedString("Import") - importBtn.bezelStyle = .rounded - importBtn.target = self - importBtn.action = #selector(self.importSettings) - - let exportBtn: NSButton = NSButton() - exportBtn.title = localizedString("Export") - exportBtn.bezelStyle = .rounded - exportBtn.target = self - exportBtn.action = #selector(self.exportSettings) - - let resetBtn: NSButton = NSButton() - resetBtn.title = localizedString("Reset") - resetBtn.bezelStyle = .rounded - resetBtn.target = self - resetBtn.action = #selector(self.resetSettings) - resetBtn.widthAnchor.constraint(equalToConstant: 225).isActive = true - - grid.addRow(with: [ - self.titleView(localizedString("Settings")), - importBtn - ]) - grid.addRow(with: [NSGridCell.emptyContentView, exportBtn]) - grid.addRow(with: [NSGridCell.emptyContentView, resetBtn]) - - view.addArrangedSubview(grid) - - return view - } - - private func buttonsView() -> NSView { - let view: NSView = NSView(frame: NSRect(x: 0, y: 0, width: self.frame.width, height: 60)) - view.heightAnchor.constraint(equalToConstant: 60).isActive = true - - let row = NSStackView() - row.translatesAutoresizingMaskIntoConstraints = false - row.orientation = .vertical - row.alignment = .centerY - row.distribution = .fill - self.buttonsContainer = row - - let uninstall: NSButton = NSButton() - uninstall.title = localizedString("Uninstall fan helper") - uninstall.bezelStyle = .rounded - uninstall.target = self - uninstall.action = #selector(self.uninstallHelper) - self.uninstallHelperButton = uninstall - - row.addArrangedSubview(uninstall) - view.addSubview(row) - - NSLayoutConstraint.activate([ - row.centerXAnchor.constraint(equalTo: view.centerXAnchor), - row.centerYAnchor.constraint(equalTo: view.centerYAnchor) - ]) - - self.buttons = view - return view - } - - // MARK: - helpers - - private func separatorView() -> NSBox { - let view = NSBox() - view.boxType = .separator - return view - } - - private func titleView(_ value: String) -> NSTextField { - let field: NSTextField = TextView(frame: NSRect(x: 0, y: 0, width: 120, height: 17)) - field.font = NSFont.systemFont(ofSize: 13, weight: .regular) - field.textColor = .secondaryLabelColor - field.stringValue = value - - return field - } - - private func toggleView(action: Selector, state: Bool, text: String) -> NSButton { - let button: NSButton = NSButton(frame: NSRect(x: 0, y: 0, width: 30, height: 20)) - button.setButtonType(.switch) - button.state = state ? .on : .off - button.title = text - button.action = action - button.isBordered = false - button.isTransparent = false - button.target = self - - return button - } - - private func toggleCombinedModulesView() { - self.combinedModules?.isHidden = !self.combinedModulesState - self.combinedModulesSeparator?.isHidden = !self.combinedModulesState - } - - private func toggleButtonsView() { - self.buttons?.isHidden = !SMCHelper.shared.isInstalled - self.buttonsSeparator?.isHidden = !SMCHelper.shared.isInstalled - } - // MARK: - actions @objc private func updateAction(_ sender: NSObject) { @@ -444,6 +286,29 @@ class ApplicationSettings: NSStackView { } } + @objc private func toggleTelemetry(_ sender: NSButton) { + telemetry.isEnabled = sender.state == NSControl.StateValue.on + } + + @objc private func toggleCombinedModules(_ sender: NSButton) { + self.combinedModulesState = sender.state == NSControl.StateValue.on + self.combinedModulesView?.toggleVisibility(1, newState: self.combinedModulesState) + self.combinedModulesView?.toggleVisibility(2, newState: self.combinedModulesState) + self.combinedModulesView?.toggleVisibility(3, newState: self.combinedModulesState) + NotificationCenter.default.post(name: .toggleOneView, object: nil, userInfo: nil) + } + + @objc private func toggleCombinedModulesSpacing(_ sender: NSMenuItem) { + guard let key = sender.representedObject as? String else { return } + self.combinedModulesSpacing = key + NotificationCenter.default.post(name: .moduleRearrange, object: nil, userInfo: nil) + } + + @objc private func toggleCombinedModulesPopup(_ sender: NSButton) { + self.combinedModulesPopup = sender.state == NSControl.StateValue.on + NotificationCenter.default.post(name: .combinedModulesPopup, object: nil, userInfo: nil) + } + @objc private func importSettings(_ sender: NSObject) { let panel = NSOpenPanel() panel.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.modalPanelWindow))) @@ -486,40 +351,15 @@ class ApplicationSettings: NSStackView { } @objc private func toggleUninstallHelperButton(_ notification: Notification) { - guard let state = notification.userInfo?["state"] as? Bool, let v = self.uninstallHelperButton else { + guard let state = notification.userInfo?["state"] as? Bool, let v = self.fanHelperView else { return } - if state && v.superview == nil { - self.buttonsContainer?.addArrangedSubview(v) - } else if !state && v.superview != nil { - v.removeFromSuperview() - } + v.isHidden = !state } @objc private func uninstallHelper() { SMCHelper.shared.uninstall() } - - @objc private func toggleCombinedModules(_ sender: NSButton) { - self.combinedModulesState = sender.state == NSControl.StateValue.on - self.toggleCombinedModulesView() - NotificationCenter.default.post(name: .toggleOneView, object: nil, userInfo: nil) - } - - @objc private func toggleCombinedModulesSpacing(_ sender: NSMenuItem) { - guard let key = sender.representedObject as? String else { return } - self.combinedModulesSpacing = key - NotificationCenter.default.post(name: .moduleRearrange, object: nil, userInfo: nil) - } - - @objc private func toggleTelemetry(_ sender: NSButton) { - telemetry.isEnabled = sender.state == NSControl.StateValue.on - } - - @objc private func toggleCombinedModulesPopup(_ sender: NSButton) { - self.combinedModulesPopup = sender.state == NSControl.StateValue.on - NotificationCenter.default.post(name: .combinedModulesPopup, object: nil, userInfo: nil) - } } private class ModuleSelectorView: NSStackView { @@ -562,6 +402,7 @@ private class ModuleSelectorView: NSStackView { background.setFrameSize(NSSize(width: w, height: self.frame.height)) self.widthAnchor.constraint(equalToConstant: w).isActive = true + self.heightAnchor.constraint(equalToConstant: self.frame.height).isActive = true } required init?(coder: NSCoder) { diff --git a/Stats/Views/CombinedView.swift b/Stats/Views/CombinedView.swift index 564a625c..53c76938 100644 --- a/Stats/Views/CombinedView.swift +++ b/Stats/Views/CombinedView.swift @@ -12,9 +12,9 @@ import Cocoa import Kit -class CombinedView { +class CombinedView: NSObject, NSGestureRecognizerDelegate { private var menuBarItem: NSStatusItem? = nil - private var view: NSView = NSView(frame: NSRect(x: 0, y: 0, width: 100, height: Constants.Widget.height)) + private var view: NSView = NSView(frame: NSRect(x: 0, y: 0, width: 0, height: Constants.Widget.height)) private var popup: PopupWindow? = nil private var status: Bool { @@ -24,7 +24,18 @@ class CombinedView { CGFloat(Int(Store.shared.string(key: "CombinedModules_spacing", defaultValue: "")) ?? 0) } - init() { + private var activeModules: [Module] { + modules.filter({ $0.enabled }).sorted(by: { $0.combinedPosition < $1.combinedPosition }) + } + + private var combinedModulesPopup: Bool { + get { Store.shared.bool(key: "CombinedModules_popup", defaultValue: true) } + set { Store.shared.set(key: "CombinedModules_popup", value: newValue) } + } + + override init() { + super.init() + modules.forEach { (m: Module) in m.menuBar.callback = { [weak self] in if let s = self?.status, s { @@ -43,6 +54,7 @@ class CombinedView { NotificationCenter.default.addObserver(self, selector: #selector(listenForOneView), name: .toggleOneView, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(listenForModuleRearrrange), name: .moduleRearrange, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(listenCombinedModulesPopup), name: .combinedModulesPopup, object: nil) } deinit { @@ -54,9 +66,26 @@ class CombinedView { self.menuBarItem?.autosaveName = "CombinedModules" self.menuBarItem?.button?.addSubview(self.view) - self.menuBarItem?.button?.target = self - self.menuBarItem?.button?.action = #selector(self.togglePopup) - self.menuBarItem?.button?.sendAction(on: [.leftMouseDown, .rightMouseDown]) + if !self.combinedModulesPopup { + self.activeModules.forEach { (m: Module) in + m.menuBar.widgets.forEach { w in + w.item.onClick = { + if let window = w.item.window { + NotificationCenter.default.post(name: .togglePopup, object: nil, userInfo: [ + "module": m.name, + "widget": w.type, + "origin": window.frame.origin, + "center": window.frame.width/2 + ]) + } + } + } + } + } else { + self.menuBarItem?.button?.target = self + self.menuBarItem?.button?.action = #selector(self.togglePopup) + self.menuBarItem?.button?.sendAction(on: [.leftMouseDown, .rightMouseDown]) + } DispatchQueue.main.async(execute: { self.recalculate() @@ -64,6 +93,11 @@ class CombinedView { } public func disable() { + self.activeModules.forEach { (m: Module) in + m.menuBar.widgets.forEach { w in + w.item.onClick = nil + } + } if let item = self.menuBarItem { NSStatusBar.system.removeStatusItem(item) } @@ -75,7 +109,7 @@ class CombinedView { var w: CGFloat = 0 var i: Int = 0 - modules.filter({ $0.enabled }).sorted(by: { $0.combinedPosition < $1.combinedPosition }).forEach { (m: Module) in + self.activeModules.forEach { (m: Module) in self.view.addSubview(m.menuBar.view) self.view.subviews[i].setFrameOrigin(NSPoint(x: w, y: 0)) w += m.menuBar.view.frame.width + self.spacing @@ -88,7 +122,7 @@ class CombinedView { // call when popup appear/disappear private func visibilityCallback(_ state: Bool) {} - @objc private func togglePopup(_ sender: Any) { + @objc private func togglePopup(_ sender: NSButton) { guard let popup = self.popup, let item = self.menuBarItem, let window = item.button?.window else { return } let openedWindows = NSApplication.shared.windows.filter{ $0 is NSPanel } openedWindows.forEach{ $0.setIsVisible(false) } @@ -127,6 +161,36 @@ class CombinedView { @objc private func listenForModuleRearrrange() { self.recalculate() } + + @objc private func listenCombinedModulesPopup() { + if !self.combinedModulesPopup { + self.activeModules.forEach { (m: Module) in + m.menuBar.widgets.forEach { w in + w.item.onClick = { + if let window = w.item.window { + NotificationCenter.default.post(name: .togglePopup, object: nil, userInfo: [ + "module": m.name, + "widget": w.type, + "origin": window.frame.origin, + "center": window.frame.width/2 + ]) + } + } + } + } + self.menuBarItem?.button?.action = nil + } else { + self.activeModules.forEach { (m: Module) in + m.menuBar.widgets.forEach { w in + w.item.onClick = nil + } + } + + self.menuBarItem?.button?.target = self + self.menuBarItem?.button?.action = #selector(self.togglePopup) + self.menuBarItem?.button?.sendAction(on: [.leftMouseDown, .rightMouseDown]) + } + } } private class Popup: NSStackView, Popup_p {