feat: redesigned App Settings to new style (Ventura-like)
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
27
Stats/Supporting Files/Assets.xcassets/cancel.imageset/Contents.json
vendored
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
BIN
Stats/Supporting Files/Assets.xcassets/cancel.imageset/baseline_cancel_black_24pt_1x.png
vendored
Normal file
|
After Width: | Height: | Size: 223 B |
BIN
Stats/Supporting Files/Assets.xcassets/cancel.imageset/baseline_cancel_black_24pt_2x.png
vendored
Normal file
|
After Width: | Height: | Size: 390 B |
BIN
Stats/Supporting Files/Assets.xcassets/cancel.imageset/baseline_cancel_black_24pt_3x.png
vendored
Normal file
|
After Width: | Height: | Size: 541 B |
27
Stats/Supporting Files/Assets.xcassets/export.imageset/Contents.json
vendored
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
BIN
Stats/Supporting Files/Assets.xcassets/export.imageset/baseline_ios_share_black_24pt_1x.png
vendored
Normal file
|
After Width: | Height: | Size: 167 B |
BIN
Stats/Supporting Files/Assets.xcassets/export.imageset/baseline_ios_share_black_24pt_2x.png
vendored
Normal file
|
After Width: | Height: | Size: 266 B |
BIN
Stats/Supporting Files/Assets.xcassets/export.imageset/baseline_ios_share_black_24pt_3x.png
vendored
Normal file
|
After Width: | Height: | Size: 362 B |
27
Stats/Supporting Files/Assets.xcassets/import.imageset/Contents.json
vendored
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
BIN
Stats/Supporting Files/Assets.xcassets/import.imageset/baseline_download_black_24pt_1x.png
vendored
Normal file
|
After Width: | Height: | Size: 116 B |
BIN
Stats/Supporting Files/Assets.xcassets/import.imageset/baseline_download_black_24pt_2x.png
vendored
Normal file
|
After Width: | Height: | Size: 156 B |
BIN
Stats/Supporting Files/Assets.xcassets/import.imageset/baseline_download_black_24pt_3x.png
vendored
Normal file
|
After Width: | Height: | Size: 185 B |
27
Stats/Supporting Files/Assets.xcassets/trash.imageset/Contents.json
vendored
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
BIN
Stats/Supporting Files/Assets.xcassets/trash.imageset/baseline_delete_black_24pt_1x.png
vendored
Normal file
|
After Width: | Height: | Size: 113 B |
BIN
Stats/Supporting Files/Assets.xcassets/trash.imageset/baseline_delete_black_24pt_2x.png
vendored
Normal file
|
After Width: | Height: | Size: 149 B |
BIN
Stats/Supporting Files/Assets.xcassets/trash.imageset/baseline_delete_black_24pt_3x.png
vendored
Normal file
|
After Width: | Height: | Size: 193 B |
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||