mirror of
https://github.com/morgan9e/macos-stats
synced 2026-04-14 00:04:15 +09:00
feat: added an option to pause/resume the application (#834)
This commit is contained in:
@@ -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..<points.count {
|
||||
linePath.line(to: CGPoint(x: points[i].0, y: points[i].1))
|
||||
}
|
||||
color.setStroke()
|
||||
linePath.lineWidth = lineWidth
|
||||
linePath.stroke()
|
||||
return linePath
|
||||
}
|
||||
|
||||
private func drawUnderLine(_ rect: NSRect, path: NSBezierPath, color: NSColor, x: CGFloat, y: CGFloat) {
|
||||
let underLinePath = path.copy() as! NSBezierPath
|
||||
underLinePath.line(to: CGPoint(x: x, y: y))
|
||||
underLinePath.line(to: CGPoint(x: x, y: y))
|
||||
underLinePath.close()
|
||||
underLinePath.addClip()
|
||||
color.withAlphaComponent(0.5).setFill()
|
||||
NSBezierPath(rect: rect).fill()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,11 +24,11 @@ public struct module_c {
|
||||
public var name: String = ""
|
||||
public var icon: NSImage?
|
||||
|
||||
var defaultState: Bool = false
|
||||
var defaultWidget: widget_t = .unknown
|
||||
var availableWidgets: [widget_t] = []
|
||||
public var defaultState: Bool = false
|
||||
internal var defaultWidget: widget_t = .unknown
|
||||
internal var availableWidgets: [widget_t] = []
|
||||
|
||||
var widgetsConfig: NSDictionary = NSDictionary()
|
||||
internal var widgetsConfig: NSDictionary = NSDictionary()
|
||||
|
||||
init(in path: String) {
|
||||
let dict: NSDictionary = NSDictionary(contentsOfFile: path)!
|
||||
@@ -80,6 +80,15 @@ open class Module: Module_p {
|
||||
private let log: NextLog
|
||||
private var readers: [Reader_p] = []
|
||||
|
||||
private var pauseState: Bool {
|
||||
get {
|
||||
return Store.shared.bool(key: "pause", defaultValue: false)
|
||||
}
|
||||
set {
|
||||
Store.shared.set(key: "pause", value: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
public init(popup: Popup_p? = nil, settings: Settings_v? = nil) {
|
||||
self.config = module_c(in: Bundle(for: type(of: self)).path(forResource: "config", ofType: "plist")!)
|
||||
|
||||
@@ -99,6 +108,8 @@ open class Module: Module_p {
|
||||
}
|
||||
|
||||
return
|
||||
} else if self.pauseState {
|
||||
self.disable()
|
||||
}
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(listenForMouseDownInSettings), name: .clickInSettings, object: nil)
|
||||
@@ -116,6 +127,10 @@ open class Module: Module_p {
|
||||
self.settings = Settings(config: &self.config, widgets: &self.menuBar.widgets, enabled: self.enabled, moduleSettings: self.settingsView)
|
||||
self.settings?.toggleCallback = { [weak self] in
|
||||
self?.toggleEnabled()
|
||||
if self?.pauseState == true {
|
||||
self?.pauseState = false
|
||||
NotificationCenter.default.post(name: .pause, object: nil, userInfo: ["state": false])
|
||||
}
|
||||
}
|
||||
|
||||
self.popup = PopupWindow(title: self.config.name, view: self.popupView, visibilityCallback: self.visibilityCallback)
|
||||
@@ -168,6 +183,7 @@ open class Module: Module_p {
|
||||
reader.start()
|
||||
}
|
||||
self.menuBar.enable()
|
||||
self.settings?.setState(self.enabled)
|
||||
debug("Module enabled", log: self.log)
|
||||
}
|
||||
|
||||
@@ -176,9 +192,12 @@ open class Module: Module_p {
|
||||
guard self.available else { return }
|
||||
|
||||
self.enabled = false
|
||||
Store.shared.set(key: "\(self.config.name)_state", value: false)
|
||||
if !self.pauseState { // omit saving the disable state when toggle by pause, need for resume state restoration
|
||||
Store.shared.set(key: "\(self.config.name)_state", value: false)
|
||||
}
|
||||
self.readers.forEach{ $0.stop() }
|
||||
self.menuBar.disable()
|
||||
self.settings?.setState(self.enabled)
|
||||
self.popup?.setIsVisible(false)
|
||||
debug("Module disabled", log: self.log)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import Cocoa
|
||||
|
||||
public protocol Settings_p: NSView {
|
||||
var toggleCallback: () -> 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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"])
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user