feat: redesigned App Settings to new style (Ventura-like)

This commit is contained in:
Serhiy Mytrovtsiy
2024-04-12 20:28:02 +02:00
parent 10c69b2bd3
commit 5a34aea381
20 changed files with 446 additions and 308 deletions

View File

@@ -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

View File

@@ -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")
}
}

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 B

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 B

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

View File

@@ -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) {

View File

@@ -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 {