From 4056b61616b70cfc1ab5a297e8843c926fdb2044 Mon Sep 17 00:00:00 2001 From: Serhiy Mytrovtsiy Date: Sat, 10 Sep 2022 12:08:46 +0200 Subject: [PATCH] feat: added an option to pause/resume the application (#834) --- Kit/helpers.swift | 101 ++++++++++++++++++++++++++++++++++ Kit/module/module.swift | 29 ++++++++-- Kit/module/settings.swift | 5 ++ Kit/types.swift | 1 + Stats/AppDelegate.swift | 9 ++- Stats/Views/AppSettings.swift | 34 ++++++++++++ Stats/Views/Settings.swift | 9 ++- Stats/helpers.swift | 32 +++++++++++ 8 files changed, 213 insertions(+), 7 deletions(-) diff --git a/Kit/helpers.swift b/Kit/helpers.swift index efb6f71b..6eb5cccb 100644 --- a/Kit/helpers.swift +++ b/Kit/helpers.swift @@ -1132,3 +1132,104 @@ public func restoreNSStatusItemPosition(id: String) { Store.shared.remove("NSStatusItem Restore Position \(id)") } } + +public class AppIcon: NSView { + public static let size: CGSize = CGSize(width: 16, height: 16) + + public init() { + super.init(frame: NSRect(x: 0, y: 3, width: AppIcon.size.width, height: AppIcon.size.height)) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func draw(_ dirtyRect: NSRect) { + super.draw(dirtyRect) + + guard let ctx = NSGraphicsContext.current?.cgContext else { return } + ctx.setShouldAntialias(true) + + NSColor.textColor.set() + NSBezierPath(roundedRect: NSRect( + x: 0, + y: 0, + width: AppIcon.size.width, + height: AppIcon.size.height + ), xRadius: 4, yRadius: 4).fill() + + NSColor.controlTextColor.set() + NSBezierPath(roundedRect: NSRect( + x: 1.5, + y: 1.5, + width: AppIcon.size.width - 3, + height: AppIcon.size.height - 3 + ), xRadius: 3, yRadius: 3).fill() + + let lineWidth = 1 / (NSScreen.main?.backingScaleFactor ?? 1) / 2 + let offset = lineWidth/2 + let zero = (AppIcon.size.height - 3 + 1.5)/2 + lineWidth + let x = 1.5 + + let downloadLine = drawLine(points: [ + (x+0, zero-offset), + (x+1, zero-offset), + (x+2, zero-offset-2.5), + (x+3, zero-offset-4), + (x+4, zero-offset), + (x+5, zero-offset-2), + (x+6, zero-offset), + (x+7, zero-offset), + (x+8, zero-offset-2), + (x+9, zero-offset), + (x+10, zero-offset-4), + (x+11, zero-offset-0.5), + (x+12, zero-offset) + ], color: NSColor.systemBlue, lineWidth: lineWidth) + + let uploadLine = drawLine(points: [ + (x+0, zero+offset), + (x+1, zero+offset), + (x+2, zero+offset+2), + (x+3, zero+offset), + (x+4, zero+offset), + (x+5, zero+offset), + (x+6, zero+offset+3), + (x+7, zero+offset+3), + (x+8, zero+offset), + (x+9, zero+offset+1), + (x+10, zero+offset+5), + (x+11, zero+offset), + (x+12, zero+offset) + ], color: NSColor.systemRed, lineWidth: lineWidth) + + ctx.saveGState() + drawUnderLine(dirtyRect, path: downloadLine, color: NSColor.systemBlue, x: x, y: zero-offset) + ctx.restoreGState() + ctx.saveGState() + drawUnderLine(dirtyRect, path: uploadLine, color: NSColor.systemRed, x: x, y: zero+offset) + ctx.restoreGState() + } + + private func drawLine(points: [(CGFloat, CGFloat)], color: NSColor, lineWidth: CGFloat) -> NSBezierPath { + let linePath = NSBezierPath() + linePath.move(to: CGPoint(x: points[0].0, y: points[0].1)) + for i in 1.. Void { get set } + func setState(_ newState: Bool) } public protocol Settings_v: NSView { @@ -214,6 +215,10 @@ open class Settings: NSStackView, Settings_p { } } + public func setState(_ newState: Bool) { + toggleNSControlState(self.enableControl, state: newState ? .on : .off) + } + private func loadWidget() { self.loadModuleSettings() self.loadWidgetSettings() diff --git a/Kit/types.swift b/Kit/types.swift index 4eca442e..f1ef2fab 100644 --- a/Kit/types.swift +++ b/Kit/types.swift @@ -207,6 +207,7 @@ public extension Notification.Name { static let syncFansControl = Notification.Name("syncFansControl") static let toggleOneView = Notification.Name("toggleOneView") static let widgetRearrange = Notification.Name("widgetRearrange") + static let pause = Notification.Name("pause") } public var isARM: Bool { diff --git a/Stats/AppDelegate.swift b/Stats/AppDelegate.swift index 91411bd5..f1aabf5d 100755 --- a/Stats/AppDelegate.swift +++ b/Stats/AppDelegate.swift @@ -39,6 +39,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele internal let setupWindow: SetupWindow = SetupWindow() internal let updateActivity = NSBackgroundActivityScheduler(identifier: "eu.exelban.Stats.updateCheck") internal var clickInNotification: Bool = false + internal var menuBarItem: NSStatusItem? = nil + + internal var pauseState: Bool { + Store.shared.bool(key: "pause", defaultValue: false) + } static func main() { let app = NSApplication.shared @@ -56,8 +61,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele modules.forEach{ $0.mount() } self.settingsWindow.setModules() } - self.defaultValues() + self.icon() + + NotificationCenter.default.addObserver(self, selector: #selector(listenForAppPause), name: .pause, object: nil) info("Stats started in \((startingPoint.timeIntervalSinceNow * -1).rounded(toPlaces: 4)) seconds") } diff --git a/Stats/Views/AppSettings.swift b/Stats/Views/AppSettings.swift index e2505df4..4a8b7820 100644 --- a/Stats/Views/AppSettings.swift +++ b/Stats/Views/AppSettings.swift @@ -28,9 +28,19 @@ class ApplicationSettings: NSStackView { } } + private var pauseState: Bool { + get { + return Store.shared.bool(key: "pause", defaultValue: false) + } + set { + Store.shared.set(key: "pause", value: newValue) + } + } + private let updateWindow: UpdateWindow = UpdateWindow() private var updateSelector: NSPopUpButton? private var startAtLoginBtn: NSButton? + private var pauseButton: NSButton? init() { super.init(frame: NSRect( @@ -49,12 +59,18 @@ class ApplicationSettings: NSStackView { self.addArrangedSubview(self.settingsView()) self.addArrangedSubview(self.separatorView()) self.addArrangedSubview(self.buttonsView()) + + NotificationCenter.default.addObserver(self, selector: #selector(listenForPause), name: .pause, object: nil) } required public init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + deinit { + NotificationCenter.default.removeObserver(self) + } + public func viewWillAppear() { self.startAtLoginBtn?.state = LaunchAtLogin.isEnabled ? .on : .off @@ -183,7 +199,15 @@ class ApplicationSettings: NSStackView { reset.target = self reset.action = #selector(self.resetSettings) + let pause: NSButton = NSButton() + pause.title = localizedString(self.pauseState ? "Resume the Stats" : "Pause the Stats") + pause.bezelStyle = .rounded + pause.target = self + pause.action = #selector(self.togglePause) + self.pauseButton = pause + view.addArrangedSubview(reset) + view.addArrangedSubview(pause) return view } @@ -288,4 +312,14 @@ class ApplicationSettings: NSStackView { } } } + + @objc func togglePause(_ sender: NSButton) { + self.pauseState = !self.pauseState + self.pauseButton?.title = localizedString(self.pauseState ? "Resume the Stats" : "Pause the Stats") + NotificationCenter.default.post(name: .pause, object: nil, userInfo: ["state": self.pauseState]) + } + + @objc func listenForPause() { + self.pauseButton?.title = localizedString(self.pauseState ? "Resume the Stats" : "Pause the Stats") + } } diff --git a/Stats/Views/Settings.swift b/Stats/Views/Settings.swift index aa455974..2b3f12a9 100644 --- a/Stats/Views/Settings.swift +++ b/Stats/Views/Settings.swift @@ -15,6 +15,10 @@ import Kit class SettingsWindow: NSWindow, NSWindowDelegate { private let viewController: SettingsViewController = SettingsViewController() + private var pauseState: Bool { + Store.shared.bool(key: "pause", defaultValue: false) + } + init() { super.init( contentRect: NSRect( @@ -76,6 +80,9 @@ class SettingsWindow: NSWindow, NSWindowDelegate { self.setIsVisible(true) self.makeKeyAndOrderFront(nil) } + if !self.isKeyWindow { + self.orderFrontRegardless() + } if let name = notification.userInfo?["module"] as? String { self.viewController.openMenu(name) @@ -84,7 +91,7 @@ class SettingsWindow: NSWindow, NSWindowDelegate { public func setModules() { self.viewController.setModules(modules) - if modules.filter({ $0.enabled != false && $0.available != false && !$0.menuBar.widgets.filter({ $0.isActive }).isEmpty }).isEmpty { + if !self.pauseState && modules.filter({ $0.enabled != false && $0.available != false && !$0.menuBar.widgets.filter({ $0.isActive }).isEmpty }).isEmpty { self.setIsVisible(true) } } diff --git a/Stats/helpers.swift b/Stats/helpers.swift index c79528f6..d8f6878f 100644 --- a/Stats/helpers.swift +++ b/Stats/helpers.swift @@ -237,4 +237,36 @@ extension AppDelegate { self.updateWindow.open(version) }) } + + @objc internal func listenForAppPause() { + for m in modules { + if self.pauseState && m.enabled { + m.disable() + } else if !self.pauseState && !m.enabled && Store.shared.bool(key: "\(m.config.name)_state", defaultValue: m.config.defaultState) { + m.enable() + } + } + self.icon() + } + + internal func icon() { + if self.pauseState { + self.menuBarItem = NSStatusBar.system.statusItem(withLength: AppIcon.size.width) + self.menuBarItem?.autosaveName = "Stats" + self.menuBarItem?.button?.addSubview(AppIcon()) + + self.menuBarItem?.button?.target = self + self.menuBarItem?.button?.action = #selector(self.openSettings) + self.menuBarItem?.button?.sendAction(on: [.leftMouseDown, .rightMouseDown]) + } else { + if let item = self.menuBarItem { + NSStatusBar.system.removeStatusItem(item) + } + self.menuBarItem = nil + } + } + + @objc internal func openSettings() { + NotificationCenter.default.post(name: .toggleSettings, object: nil, userInfo: ["module": "Dashboard"]) + } }