feat: renamed internal class Settings to Window (preparation for in-app statistics view)

This commit is contained in:
Serhiy Mytrovtsiy
2026-03-14 20:25:39 +01:00
parent 233229a8b8
commit 8a3a4ccc27
8 changed files with 185 additions and 128 deletions

View File

@@ -9,7 +9,7 @@
import Cocoa
public struct module_c {
public var name: String = ""
public var name: String
public var icon: NSImage?
public var defaultState: Bool = false
@@ -18,13 +18,19 @@ public struct module_c {
internal var widgetsConfig: NSDictionary = NSDictionary()
internal var settingsConfig: NSDictionary = NSDictionary()
internal var previewConfig: NSDictionary = NSDictionary()
public var hasPreview: Bool { self.previewConfig["enabled"] as? Bool ?? false }
init(in path: String) {
let dict: NSDictionary = NSDictionary(contentsOfFile: path)!
if let name = dict["Name"] as? String {
self.name = name
} else {
fatalError("failed to initialize module, name is missing")
}
if let state = dict["State"] as? Bool {
self.defaultState = state
}
@@ -60,6 +66,10 @@ public struct module_c {
if let settingsDict = dict["Settings"] as? NSDictionary {
self.settingsConfig = settingsDict
}
if let previewDict = dict["Preview"] as? NSDictionary {
self.previewConfig = previewDict
}
}
}
@@ -70,7 +80,7 @@ open class Module {
public var enabled: Bool = false
public var menuBar: MenuBar
public var settings: Settings_p? = nil
public var window: Window? = nil
public let portal: Portal_p?
public var name: String { config.name }
@@ -88,6 +98,7 @@ open class Module {
private var popup: PopupWindow? = nil
private var popupView: Popup_p? = nil
private var notificationsView: NotificationsWrapper? = nil
private var previewView: Preview_v? = nil
private let log: NextLog
private var readers: [Reader_p] = []
@@ -97,7 +108,14 @@ open class Module {
set { Store.shared.set(key: "pause", value: newValue) }
}
public init(moduleType: ModuleType, popup: Popup_p? = nil, settings: Settings_v? = nil, portal: Portal_p? = nil, notifications: NotificationsWrapper? = nil) {
public init(
moduleType: ModuleType,
popup: Popup_p? = nil,
settings: Settings_v? = nil,
portal: Portal_p? = nil,
notifications: NotificationsWrapper? = nil,
preview: Preview_v? = nil
) {
self.moduleType = moduleType
self.portal = portal
self.config = module_c(in: Bundle(for: type(of: self)).path(forResource: "config", ofType: "plist")!)
@@ -106,6 +124,7 @@ open class Module {
self.settingsView = settings
self.popupView = popup
self.notificationsView = notifications
self.previewView = preview
self.menuBar = MenuBar(moduleName: self.config.name)
self.available = self.isAvailable()
self.enabled = Store.shared.bool(key: "\(self.config.name)_state", defaultValue: self.config.defaultState)
@@ -128,6 +147,7 @@ open class Module {
NotificationCenter.default.addObserver(self, selector: #selector(listenForModuleToggle), name: .toggleModule, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(listenForPopupToggle), name: .togglePopup, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(listenForToggleWidget), name: .toggleWidget, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(listenForWindowOpen), name: .openWindow, object: nil)
// swiftlint:disable empty_count
if self.config.widgetsConfig.count != 0 {
@@ -137,15 +157,16 @@ open class Module {
debug("Module started without widget", log: self.log)
}
self.settings = Settings(
self.window = Window(
config: &self.config,
widgets: &self.menuBar.widgets,
modulePreview: self.previewView,
moduleSettings: self.settingsView,
popupSettings: self.popupView,
notificationsSettings: self.notificationsView
)
self.popup = PopupWindow(title: self.config.name, module: self.moduleType, view: self.popupView, visibilityCallback: self.visibilityCallback)
self.popup = PopupWindow(title: self.config.name, module: self.moduleType, view: self.popupView, visibilityCallback: self.popupVisibilityCallback)
}
deinit {
@@ -194,7 +215,7 @@ open class Module {
reader.start()
}
self.menuBar.enable()
self.settings?.setState(self.enabled)
self.window?.setState(self.enabled)
debug("Module enabled", log: self.log)
}
@@ -209,7 +230,7 @@ open class Module {
}
self.readers.forEach{ $0.stop() }
self.menuBar.disable()
self.settings?.setState(self.enabled)
self.window?.setState(self.enabled)
self.popup?.setIsVisible(false)
debug("Module disabled", log: self.log)
}
@@ -237,8 +258,26 @@ open class Module {
}
// call when popup appear/disappear
private func visibilityCallback(_ state: Bool) {
self.readers.filter{ $0.popup }.forEach { (reader: Reader_p) in
private func popupVisibilityCallback(_ state: Bool) {
self.readers.filter{ $0.popup || $0.sleep }.forEach { (reader: Reader_p) in
if state {
reader.unlock()
reader.start()
} else {
reader.pause()
reader.lock()
}
}
}
@objc private func listenForWindowOpen(_ notification: Notification) {
guard var state = notification.userInfo?["state"] as? Bool else { return }
if state, let name = notification.userInfo?["module"] as? String, self.config.name != name {
state = false
}
self.readers.filter{ $0.preview || $0.sleep }.forEach { (reader: Reader_p) in
if state {
reader.unlock()
reader.start()

View File

@@ -11,21 +11,20 @@
import Cocoa
public protocol Settings_p: NSView {
func setState(_ newState: Bool)
}
public protocol Settings_v: NSView {
func load(widgets: [widget_t])
}
open class Settings: NSStackView, Settings_p {
public protocol Preview_v: NSView {}
open class Window: NSStackView {
private var config: UnsafePointer<module_c>
private var widgets: [SWidget]
private var segmentedControl: NSSegmentedControl?
private var tabView: NSTabView?
private var modulePreview: Preview_v?
private var moduleSettings: Settings_v?
private var popupSettings: Popup_p?
private var notificationsSettings: NotificationsWrapper?
@@ -50,19 +49,30 @@ open class Settings: NSStackView, Settings_p {
set { Store.shared.set(key: "\(self.config.pointee.name)_oneView", value: newValue) }
}
private var isPreviewAvailable: Bool
private var isPopupSettingsAvailable: Bool
private var isNotificationsSettingsAvailable: Bool
private var previewView: NSView? = nil
private var settingsView: NSView? = nil
init(config: UnsafePointer<module_c>, widgets: UnsafeMutablePointer<[SWidget]>, moduleSettings: Settings_v?, popupSettings: Popup_p?, notificationsSettings: NotificationsWrapper?) {
init(
config: UnsafePointer<module_c>,
widgets: UnsafeMutablePointer<[SWidget]>,
modulePreview: Preview_v?,
moduleSettings: Settings_v?,
popupSettings: Popup_p?,
notificationsSettings: NotificationsWrapper?
) {
self.config = config
self.widgets = widgets.pointee
self.modulePreview = modulePreview
self.moduleSettings = moduleSettings
self.popupSettings = popupSettings
self.notificationsSettings = notificationsSettings
self.isPreviewAvailable = config.pointee.previewConfig["enabled"] as? Bool ?? false
self.isPopupSettingsAvailable = config.pointee.settingsConfig["popup"] as? Bool ?? false
self.isNotificationsSettingsAvailable = config.pointee.settingsConfig["notifications"] as? Bool ?? false
@@ -79,22 +89,27 @@ open class Settings: NSStackView, Settings_p {
right: Constants.Settings.margin
)
let header = self.header()
let settingsView = self.settings()
self.settingsView = settingsView
let previewView = self.preview()
self.previewView = previewView
self.addArrangedSubview(header)
self.addArrangedSubview(settingsView)
self.addArrangedSubview(previewView)
NotificationCenter.default.addObserver(self, selector: #selector(listenForOneView), name: .toggleOneView, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(listenForToggleView), name: .togglePreview, object: nil)
self.segmentedControl?.widthAnchor.constraint(equalTo: self.widthAnchor, constant: -(Constants.Settings.margin*2)).isActive = true
if self.isPreviewAvailable {
self.toggleView()
}
}
deinit {
NotificationCenter.default.removeObserver(self, name: .toggleOneView, object: nil)
NotificationCenter.default.removeObserver(self, name: .togglePreview, object: nil)
}
required public init?(coder: NSCoder) {
@@ -105,28 +120,20 @@ open class Settings: NSStackView, Settings_p {
toggleNSControlState(self.enableControl, state: newState ? .on : .off)
}
private func header() -> NSView {
let view = NSStackView()
view.orientation = .horizontal
view.spacing = Constants.Settings.margin
let widgetSelector = WidgetSelectorView(module: self.config.pointee.name, widgets: self.widgets, stateCallback: self.loadWidget)
// let button = ButtonSelectorView { [weak self] in
// self?.toggleView()
// }
view.addArrangedSubview(widgetSelector)
// view.addArrangedSubview(button)
return view
}
private func preview() -> NSView {
let view = NSStackView()
view.isHidden = true
view.orientation = .vertical
view.addArrangedSubview(EmptyView(height: 0, msg: localizedString("Preview is not available for that module")))
return view
let container = NSStackView()
container.isHidden = true
container.orientation = .vertical
var view: NSView = EmptyView(height: 0, msg: localizedString("Preview is not available for that module"))
if self.isPreviewAvailable, let v = self.modulePreview {
view = v
}
container.addArrangedSubview(view)
return container
}
private func settings() -> NSView {
@@ -208,6 +215,9 @@ open class Settings: NSStackView, Settings_p {
tabView.addTabViewItem(notificationsTab)
}
let widgetSelector = WidgetSelectorView(module: self.config.pointee.name, widgets: self.widgets, stateCallback: self.loadWidget)
view.addArrangedSubview(widgetSelector)
view.addArrangedSubview(segmentedControl)
view.addArrangedSubview(tabView)
@@ -296,9 +306,12 @@ open class Settings: NSStackView, Settings_p {
}
}
@objc private func toggleView() {
@objc private func listenForToggleView(_ notification: Notification) {
guard let moduleName = notification.userInfo?["module"], self.config.pointee.name == moduleName as? String else { return }
self.toggleView()
}
private func toggleView() {
guard let preview = self.previewView, let settings = self.settingsView else { return }
preview.isHidden = !preview.isHidden
settings.isHidden = !settings.isHidden
}
@@ -310,7 +323,7 @@ private class WidgetSelectorView: NSStackView {
private var moved: Bool = false
private var background: NSVisualEffectView = {
let view = NSVisualEffectView(frame: NSRect.zero)
let view = NSVisualEffectView(frame: .zero)
view.blendingMode = .withinWindow
view.translatesAutoresizingMaskIntoConstraints = false
if #available(macOS 26.0, *) {
@@ -666,80 +679,3 @@ private class WidgetSettings: NSStackView {
return container
}
}
private class ButtonSelectorView: NSStackView {
private var callback: () -> Void
private var background: NSVisualEffectView = {
let view = NSVisualEffectView(frame: NSRect.zero)
view.blendingMode = .withinWindow
view.material = .contentBackground
view.state = .active
view.wantsLayer = true
view.layer?.cornerRadius = 5
return view
}()
private var settingsIcon: NSImage { iconFromSymbol(name: "gear", scale: .large) }
private var previewIcon: NSImage { iconFromSymbol(name: "command", scale: .large) }
private var button: NSButton? = nil
private var isSettingsEnabled: Bool = false
fileprivate init(callback: @escaping () -> Void) {
self.callback = callback
super.init(frame: NSRect.zero)
self.heightAnchor.constraint(equalToConstant: Constants.Widget.height + (Constants.Settings.margin*2)).isActive = true
self.translatesAutoresizingMaskIntoConstraints = false
self.edgeInsets = NSEdgeInsets(
top: Constants.Settings.margin,
left: Constants.Settings.margin,
bottom: Constants.Settings.margin,
right: Constants.Settings.margin
)
self.spacing = Constants.Settings.margin
self.addSubview(self.background, positioned: .below, relativeTo: .none)
let button = NSButton()
button.toolTip = localizedString("Open module settings")
button.bezelStyle = .regularSquare
button.translatesAutoresizingMaskIntoConstraints = false
button.imageScaling = .scaleNone
button.image = self.settingsIcon
button.contentTintColor = .secondaryLabelColor
button.isBordered = false
button.action = #selector(self.action)
button.target = self
button.focusRingType = .none
button.widthAnchor.constraint(equalToConstant: Constants.Widget.height).isActive = true
self.button = button
self.addArrangedSubview(button)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func updateLayer() {
self.background.setFrameSize(self.frame.size)
}
@objc private func action() {
guard let button = self.button else { return }
self.callback()
self.isSettingsEnabled = !self.isSettingsEnabled
if self.isSettingsEnabled {
button.image = self.previewIcon
button.toolTip = localizedString("Close module settings")
} else {
button.image = self.settingsIcon
button.toolTip = localizedString("Open module settings")
}
}
}

View File

@@ -26,7 +26,7 @@ public class ProcessesView: NSStackView {
private var list: [ProcessView] = []
private var colorViews: [ColorView] = []
public init(frame: NSRect, values: [ProcessHeader], n: Int = 0) {
public init(frame: NSRect = .zero, values: [ProcessHeader], n: Int = 0) {
super.init(frame: frame)
self.orientation = .vertical

View File

@@ -266,6 +266,7 @@ public extension Notification.Name {
static let toggleModule = Notification.Name("toggleModule")
static let togglePopup = Notification.Name("togglePopup")
static let toggleWidget = Notification.Name("toggleWidget")
static let togglePreview = Notification.Name("togglePreview")
static let openModuleSettings = Notification.Name("openModuleSettings")
static let clickInSettings = Notification.Name("clickInSettings")
static let refreshPublicIP = Notification.Name("refreshPublicIP")
@@ -281,6 +282,7 @@ public extension Notification.Name {
static let combinedModulesPopup = Notification.Name("combinedModulesPopup")
static let remoteLoginSuccess = Notification.Name("remoteLoginSuccess")
static let remoteState = Notification.Name("remoteState")
static let openWindow = Notification.Name("openWindow")
}
public var isARM: Bool {

View File

@@ -104,7 +104,7 @@
9A2847672666AA2700EC1F6D /* BarChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A28475D2666AA2700EC1F6D /* BarChart.swift */; };
9A2847682666AA2700EC1F6D /* Stack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A28475E2666AA2700EC1F6D /* Stack.swift */; };
9A2847792666AA5000EC1F6D /* module.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2847742666AA5000EC1F6D /* module.swift */; };
9A28477A2666AA5000EC1F6D /* settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2847752666AA5000EC1F6D /* settings.swift */; };
9A28477A2666AA5000EC1F6D /* window.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2847752666AA5000EC1F6D /* window.swift */; };
9A28477B2666AA5000EC1F6D /* popup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2847762666AA5000EC1F6D /* popup.swift */; };
9A28477C2666AA5000EC1F6D /* reader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2847772666AA5000EC1F6D /* reader.swift */; };
9A28477D2666AA5000EC1F6D /* widget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2847782666AA5000EC1F6D /* widget.swift */; };
@@ -603,7 +603,7 @@
9A28475D2666AA2700EC1F6D /* BarChart.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarChart.swift; sourceTree = "<group>"; };
9A28475E2666AA2700EC1F6D /* Stack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Stack.swift; sourceTree = "<group>"; };
9A2847742666AA5000EC1F6D /* module.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = module.swift; sourceTree = "<group>"; };
9A2847752666AA5000EC1F6D /* settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = settings.swift; sourceTree = "<group>"; };
9A2847752666AA5000EC1F6D /* window.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = window.swift; sourceTree = "<group>"; };
9A2847762666AA5000EC1F6D /* popup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = popup.swift; sourceTree = "<group>"; };
9A2847772666AA5000EC1F6D /* reader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = reader.swift; sourceTree = "<group>"; };
9A2847782666AA5000EC1F6D /* widget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = widget.swift; sourceTree = "<group>"; };
@@ -1060,7 +1060,7 @@
9A2847762666AA5000EC1F6D /* popup.swift */,
9A2847782666AA5000EC1F6D /* widget.swift */,
9A2847772666AA5000EC1F6D /* reader.swift */,
9A2847752666AA5000EC1F6D /* settings.swift */,
9A2847752666AA5000EC1F6D /* window.swift */,
5C23BC0329A014AC00DBA990 /* portal.swift */,
5CF2210C2B1E7EAF006C583F /* notifications.swift */,
);
@@ -2095,7 +2095,7 @@
9A28477D2666AA5000EC1F6D /* widget.swift in Sources */,
9A2848212666AB3600EC1F6D /* helpers.swift in Sources */,
5CAA50722C8E417700B13E13 /* Text.swift in Sources */,
9A28477A2666AA5000EC1F6D /* settings.swift in Sources */,
9A28477A2666AA5000EC1F6D /* window.swift in Sources */,
9A28475F2666AA2700EC1F6D /* LineChart.swift in Sources */,
9A302614286A2A3B00B41D57 /* Repeater.swift in Sources */,
5C4E8BA12B6EEE8E00F148B6 /* lldb.m in Sources */,

View File

@@ -17,7 +17,7 @@
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>756</string>
<string>757</string>
<key>Description</key>
<string>Simple macOS system monitor in your menu bar</string>
<key>LSApplicationCategoryType</key>

View File

@@ -14,6 +14,7 @@ import Kit
public extension NSToolbarItem.Identifier {
static let toggleButton = NSToolbarItem.Identifier("toggleButton")
static let previewButton = NSToolbarItem.Identifier("previewButton")
}
class SettingsWindow: NSWindow, NSWindowDelegate, NSToolbarDelegate {
@@ -28,6 +29,7 @@ class SettingsWindow: NSWindow, NSWindowDelegate, NSToolbarDelegate {
private var toggleButton: NSControl? = nil
private var activeModuleName: String? = nil
private var settingsPreviewButton: NSView? = nil
private var pauseState: Bool { Store.shared.bool(key: "pause", defaultValue: false) }
@@ -119,6 +121,18 @@ class SettingsWindow: NSWindow, NSWindowDelegate, NSToolbarDelegate {
func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
switch itemIdentifier {
case .previewButton:
let button = SettingsPreviewButton { [weak self] in
guard let moduleName = self?.activeModuleName else { return }
NotificationCenter.default.post(name: .togglePreview, object: nil, userInfo: ["module": moduleName])
}
self.settingsPreviewButton = button
let toolbarItem = NSToolbarItem(itemIdentifier: itemIdentifier)
toolbarItem.view = button
toolbarItem.isBordered = false
return toolbarItem
case .toggleButton:
let switchButton = NSSwitch()
switchButton.state = .on
@@ -139,10 +153,10 @@ class SettingsWindow: NSWindow, NSWindowDelegate, NSToolbarDelegate {
}
func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [.flexibleSpace, .toggleButton]
return [.flexibleSpace, .previewButton, .toggleButton]
}
func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [.flexibleSpace, .toggleButton]
return [.flexibleSpace, .previewButton, .toggleButton]
}
@objc private func toggleSettingsHandler(_ notification: Notification) {
@@ -164,19 +178,25 @@ class SettingsWindow: NSWindow, NSWindowDelegate, NSToolbarDelegate {
if let title = notification.userInfo?["module"] as? String {
var view: NSView = NSView()
if let detectedModule = modules.first(where: { $0.config.name == title }) {
if let v = detectedModule.settings {
if let v = detectedModule.window {
view = v
}
self.activeModuleName = detectedModule.config.name
toggleNSControlState(self.toggleButton, state: detectedModule.enabled ? .on : .off)
self.toggleButton?.isHidden = false
self.settingsPreviewButton?.isHidden = !detectedModule.config.hasPreview
NotificationCenter.default.post(name: .openWindow, object: nil, userInfo: ["module": detectedModule.config.name, "state": true])
} else if title == "Dashboard" {
view = self.dashboard
self.toggleButton?.isHidden = true
self.settingsPreviewButton?.isHidden = true
NotificationCenter.default.post(name: .openWindow, object: nil, userInfo: ["state": false])
} else if title == "Settings" {
self.settings.viewWillAppear()
view = self.settings
self.toggleButton?.isHidden = true
self.settingsPreviewButton?.isHidden = true
NotificationCenter.default.post(name: .openWindow, object: nil, userInfo: ["state": false])
}
self.title = localizedString(title)
@@ -523,3 +543,63 @@ private class MenuItem: NSView {
self.active = false
}
}
private class SettingsPreviewButton: NSStackView {
private var callback: () -> Void
private var settingsIcon: NSImage { iconFromSymbol(name: "gear", scale: .large) }
private var previewIcon: NSImage { iconFromSymbol(name: "command", scale: .large) }
private var button: NSButton? = nil
private var isSettingsEnabled: Bool = false
fileprivate init(callback: @escaping () -> Void) {
self.callback = callback
super.init(frame: .zero)
self.translatesAutoresizingMaskIntoConstraints = false
self.edgeInsets = NSEdgeInsets(
top: Constants.Settings.margin,
left: Constants.Settings.margin,
bottom: Constants.Settings.margin,
right: Constants.Settings.margin
)
self.spacing = Constants.Settings.margin
let button = NSButton()
button.toolTip = localizedString("Open module settings")
button.bezelStyle = .regularSquare
button.translatesAutoresizingMaskIntoConstraints = false
button.imageScaling = .scaleNone
button.image = self.settingsIcon
button.contentTintColor = .secondaryLabelColor
button.isBordered = false
button.action = #selector(self.action)
button.target = self
button.focusRingType = .none
button.widthAnchor.constraint(equalToConstant: Constants.Widget.height).isActive = true
self.button = button
self.addArrangedSubview(button)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func action() {
guard let button = self.button else { return }
self.callback()
self.isSettingsEnabled = !self.isSettingsEnabled
if self.isSettingsEnabled {
button.image = self.previewIcon
button.toolTip = localizedString("Close module settings")
} else {
button.image = self.settingsIcon
button.toolTip = localizedString("Open module settings")
}
}
}

View File

@@ -13,7 +13,7 @@
<key>CFBundleShortVersionString</key>
<string>2.12.3</string>
<key>CFBundleVersion</key>
<string>756</string>
<string>757</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>