2020-06-07 12:22:32 +02:00
//
// S e t t i n g s . s w i f t
// S t a t s
//
// C r e a t e d b y S e r h i y M y t r o v t s i y o n 1 2 / 0 4 / 2 0 2 0 .
// U s i n g S w i f t 5 . 0 .
// R u n n i n g o n m a c O S 1 0 . 1 5 .
//
// C o p y r i g h t © 2 0 2 0 S e r h i y M y t r o v t s i y . A l l r i g h t s r e s e r v e d .
//
import Cocoa
2021-06-04 19:37:29 +02:00
import Kit
2020-06-07 12:22:32 +02:00
2022-12-02 17:04:07 +01:00
public extension NSToolbarItem . Identifier {
static let toggleButton = NSToolbarItem . Identifier ( " toggleButton " )
2026-03-14 20:25:39 +01:00
static let previewButton = NSToolbarItem . Identifier ( " previewButton " )
2022-12-02 17:04:07 +01:00
}
class SettingsWindow : NSWindow , NSWindowDelegate , NSToolbarDelegate {
2026-03-06 20:49:37 +01:00
private static let size : CGSize = CGSize ( width : 720 , height : 480 )
private static let frameAutosaveName = " eu.exelban.Stats.Settings.WindowFrame "
2022-12-02 17:04:07 +01:00
private let mainView : MainView = MainView ( frame : NSRect ( x : 0 , y : 0 , width : 540 , height : 480 ) )
private let sidebarView : SidebarView = SidebarView ( frame : NSRect ( x : 0 , y : 0 , width : 180 , height : 480 ) )
private var dashboard : NSView = Dashboard ( )
private var settings : ApplicationSettings = ApplicationSettings ( )
private var toggleButton : NSControl ? = nil
private var activeModuleName : String ? = nil
2026-03-14 20:25:39 +01:00
private var settingsPreviewButton : NSView ? = nil
2020-06-07 12:22:32 +02:00
2026-03-06 20:49:37 +01:00
private var pauseState : Bool { Store . shared . bool ( key : " pause " , defaultValue : false ) }
2022-09-10 12:08:46 +02:00
2020-06-07 12:22:32 +02:00
init ( ) {
super . init (
2021-05-22 14:58:20 +02:00
contentRect : NSRect (
2022-11-30 16:56:59 +01:00
x : NSScreen . main ! . frame . width - SettingsWindow . size . width ,
y : NSScreen . main ! . frame . height - SettingsWindow . size . height ,
width : SettingsWindow . size . width ,
height : SettingsWindow . size . height
2020-06-24 00:23:06 +02:00
) ,
2026-03-06 20:49:37 +01:00
styleMask : [ . closable , . titled , . miniaturizable , . fullSizeContentView , . resizable ] ,
2020-06-07 12:22:32 +02:00
backing : . buffered ,
2020-11-13 16:29:44 +01:00
defer : false
2020-06-07 12:22:32 +02:00
)
2022-12-02 17:04:07 +01:00
let sidebarViewController = NSSplitViewController ( )
2020-06-07 12:22:32 +02:00
2022-12-02 17:04:07 +01:00
let sidebarVC : NSViewController = NSViewController ( nibName : nil , bundle : nil )
sidebarVC . view = self . sidebarView
let mainVC : NSViewController = NSViewController ( nibName : nil , bundle : nil )
mainVC . view = self . mainView
let sidebarItem = NSSplitViewItem ( sidebarWithViewController : sidebarVC )
let contentItem = NSSplitViewItem ( viewController : mainVC )
sidebarItem . canCollapse = false
contentItem . canCollapse = false
sidebarViewController . addSplitViewItem ( sidebarItem )
sidebarViewController . addSplitViewItem ( contentItem )
2020-07-14 17:28:46 +02:00
2026-03-06 20:49:37 +01:00
contentItem . minimumThickness = 540
2022-11-30 16:56:59 +01:00
let newToolbar = NSToolbar ( identifier : " eu.exelban.Stats.Settings.Toolbar " )
newToolbar . allowsUserCustomization = false
newToolbar . autosavesConfiguration = true
newToolbar . displayMode = . default
newToolbar . showsBaselineSeparator = true
2022-12-02 17:04:07 +01:00
newToolbar . delegate = self
2022-11-30 16:56:59 +01:00
2023-02-02 15:30:34 +01:00
self . toolbar = newToolbar
2022-12-02 17:04:07 +01:00
self . contentViewController = sidebarViewController
self . titlebarAppearsTransparent = true
2025-09-22 21:22:54 +02:00
if # unavailable ( macOS 26.0 ) {
self . backgroundColor = . clear
}
2026-03-06 20:49:37 +01:00
self . isRestorable = true
self . setFrameAutosaveName ( SettingsWindow . frameAutosaveName )
if ! self . setFrameUsingName ( SettingsWindow . frameAutosaveName ) {
self . positionCenter ( )
}
2022-12-04 14:52:20 +01:00
self . setIsVisible ( false )
2026-03-06 20:49:37 +01:00
self . minSize = NSSize ( width : SettingsWindow . size . width , height : SettingsWindow . size . height - Constants . Popup . headerHeight )
2022-12-02 17:04:07 +01:00
let windowController = NSWindowController ( )
windowController . window = self
windowController . loadWindow ( )
NotificationCenter . default . addObserver ( self , selector : #selector ( menuCallback ) , name : . openModuleSettings , object : nil )
2020-07-14 17:28:46 +02:00
NotificationCenter . default . addObserver ( self , selector : #selector ( toggleSettingsHandler ) , name : . toggleSettings , object : nil )
2022-12-02 17:04:07 +01:00
NotificationCenter . default . addObserver ( self , selector : #selector ( externalModuleToggle ) , name : . toggleModule , object : nil )
self . sidebarView . openMenu ( " Dashboard " )
2020-07-14 17:28:46 +02:00
}
deinit {
2022-11-30 16:56:59 +01:00
NotificationCenter . default . removeObserver ( self , name : . toggleSettings , object : nil )
2022-12-02 17:04:07 +01:00
NotificationCenter . default . removeObserver ( self , name : . openModuleSettings , object : nil )
NotificationCenter . default . removeObserver ( self , name : . toggleModule , object : nil )
2020-07-14 17:28:46 +02:00
}
2021-09-11 09:12:47 +02:00
override func performKeyEquivalent ( with event : NSEvent ) -> Bool {
if event . type = = NSEvent . EventType . keyDown && event . modifierFlags . contains ( . command ) {
if event . keyCode = = 12 || event . keyCode = = 13 {
self . setIsVisible ( false )
return true
} else if event . keyCode = = 46 {
self . miniaturize ( event )
return true
}
}
return super . performKeyEquivalent ( with : event )
}
2022-12-02 17:04:07 +01:00
override func mouseUp ( with : NSEvent ) {
NotificationCenter . default . post ( name : . clickInSettings , object : nil , userInfo : nil )
}
func toolbar ( _ toolbar : NSToolbar , itemForItemIdentifier itemIdentifier : NSToolbarItem . Identifier , willBeInsertedIntoToolbar flag : Bool ) -> NSToolbarItem ? {
switch itemIdentifier {
2026-03-14 20:25:39 +01:00
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
2022-12-02 17:04:07 +01:00
case . toggleButton :
2026-03-06 20:49:37 +01:00
let switchButton = NSSwitch ( )
switchButton . state = . on
switchButton . action = #selector ( self . toggleEnable )
switchButton . target = self
switchButton . controlSize = . small
self . toggleButton = switchButton
2022-12-02 17:04:07 +01:00
let toolbarItem = NSToolbarItem ( itemIdentifier : itemIdentifier )
2025-02-02 22:53:07 +08:00
toolbarItem . toolTip = localizedString ( " Toggle the module " )
2026-03-06 20:49:37 +01:00
toolbarItem . view = switchButton
2025-09-22 21:22:54 +02:00
toolbarItem . isBordered = false
2022-12-02 17:04:07 +01:00
return toolbarItem
default :
return nil
}
}
2024-04-09 18:42:28 +02:00
2022-12-02 17:04:07 +01:00
func toolbarAllowedItemIdentifiers ( _ toolbar : NSToolbar ) -> [ NSToolbarItem . Identifier ] {
2026-03-14 20:25:39 +01:00
return [ . flexibleSpace , . previewButton , . toggleButton ]
2022-12-02 17:04:07 +01:00
}
func toolbarDefaultItemIdentifiers ( _ toolbar : NSToolbar ) -> [ NSToolbarItem . Identifier ] {
2026-03-14 20:25:39 +01:00
return [ . flexibleSpace , . previewButton , . toggleButton ]
2022-12-02 17:04:07 +01:00
}
2020-07-14 17:28:46 +02:00
@objc private func toggleSettingsHandler ( _ notification : Notification ) {
if ! self . isVisible {
self . setIsVisible ( true )
self . makeKeyAndOrderFront ( nil )
}
2022-09-10 12:08:46 +02:00
if ! self . isKeyWindow {
self . orderFrontRegardless ( )
}
2020-07-14 17:28:46 +02:00
2023-02-22 20:17:39 +01:00
if var name = notification . userInfo ? [ " module " ] as ? String {
if name = = " Combined modules " { name = " Dashboard " }
2022-12-02 17:04:07 +01:00
self . sidebarView . openMenu ( name )
2020-07-14 17:28:46 +02:00
}
2020-06-07 12:22:32 +02:00
}
2022-11-30 16:56:59 +01:00
@objc private func menuCallback ( _ notification : Notification ) {
if let title = notification . userInfo ? [ " module " ] as ? String {
var view : NSView = NSView ( )
2022-12-02 17:04:07 +01:00
if let detectedModule = modules . first ( where : { $0 . config . name = = title } ) {
2026-03-14 20:25:39 +01:00
if let v = detectedModule . window {
2022-11-30 16:56:59 +01:00
view = v
}
2022-12-02 17:04:07 +01:00
self . activeModuleName = detectedModule . config . name
toggleNSControlState ( self . toggleButton , state : detectedModule . enabled ? . on : . off )
self . toggleButton ? . isHidden = false
2026-03-14 20:25:39 +01:00
self . settingsPreviewButton ? . isHidden = ! detectedModule . config . hasPreview
NotificationCenter . default . post ( name : . openWindow , object : nil , userInfo : [ " module " : detectedModule . config . name , " state " : true ] )
2022-11-30 16:56:59 +01:00
} else if title = = " Dashboard " {
view = self . dashboard
2022-12-02 17:04:07 +01:00
self . toggleButton ? . isHidden = true
2026-03-14 20:25:39 +01:00
self . settingsPreviewButton ? . isHidden = true
NotificationCenter . default . post ( name : . openWindow , object : nil , userInfo : [ " state " : false ] )
2022-11-30 16:56:59 +01:00
} else if title = = " Settings " {
self . settings . viewWillAppear ( )
view = self . settings
2022-12-02 17:04:07 +01:00
self . toggleButton ? . isHidden = true
2026-03-14 20:25:39 +01:00
self . settingsPreviewButton ? . isHidden = true
NotificationCenter . default . post ( name : . openWindow , object : nil , userInfo : [ " state " : false ] )
2022-11-30 16:56:59 +01:00
}
2022-12-02 17:04:07 +01:00
self . title = localizedString ( title )
self . mainView . setView ( view )
self . sidebarView . openMenu ( title )
2022-11-30 16:56:59 +01:00
}
2020-06-07 12:22:32 +02:00
}
2022-12-02 17:04:07 +01:00
@objc private func toggleEnable ( _ sender : NSControl ) {
guard let moduleName = self . activeModuleName else { return }
NotificationCenter . default . post ( name : . toggleModule , object : nil , userInfo : [ " module " : moduleName , " state " : controlState ( sender ) ] )
}
@objc private func externalModuleToggle ( _ notification : Notification ) {
if let name = notification . userInfo ? [ " module " ] as ? String , name = = self . activeModuleName {
if let state = notification . userInfo ? [ " state " ] as ? Bool {
toggleNSControlState ( self . toggleButton , state : state ? . on : . off )
}
}
}
2024-05-08 17:21:58 +02:00
internal func setModules ( ) {
2022-12-02 17:04:07 +01:00
self . sidebarView . setModules ( modules )
if ! self . pauseState && modules . filter ( { $0 . enabled != false && $0 . available != false && ! $0 . menuBar . widgets . filter ( { $0 . isActive } ) . isEmpty } ) . isEmpty {
self . setIsVisible ( true )
}
}
2023-01-24 18:05:12 +01:00
private func positionCenter ( ) {
self . setFrameOrigin ( NSPoint (
x : ( NSScreen . main ! . frame . width - SettingsWindow . size . width ) / 2 ,
2023-12-09 17:27:41 +01:00
y : ( ( NSScreen . main ! . frame . height - SettingsWindow . size . height ) / 1.75 )
2023-01-24 18:05:12 +01:00
) )
}
2020-06-07 12:22:32 +02:00
}
2022-12-02 17:04:07 +01:00
// MARK: - M a i n V i e w
private class MainView : NSView {
2026-03-06 20:49:37 +01:00
fileprivate let container : NSStackView = NSStackView ( )
2022-12-02 17:04:07 +01:00
2026-03-08 19:06:08 +01:00
private let background : NSVisualEffectView = {
let view = NSVisualEffectView ( frame : NSRect . zero )
view . blendingMode = . withinWindow
view . material = . contentBackground
view . state = . active
view . translatesAutoresizingMaskIntoConstraints = false
view . setContentHuggingPriority ( . defaultLow , for : . horizontal )
view . setContentHuggingPriority ( . defaultLow , for : . vertical )
view . setContentCompressionResistancePriority ( . defaultLow , for : . horizontal )
view . setContentCompressionResistancePriority ( . defaultLow , for : . vertical )
return view
} ( )
2022-12-02 17:04:07 +01:00
override init ( frame : NSRect ) {
2022-11-30 16:56:59 +01:00
super . init ( frame : NSRect . zero )
2026-03-08 19:06:08 +01:00
self . translatesAutoresizingMaskIntoConstraints = false
2022-12-02 17:04:07 +01:00
self . container . translatesAutoresizingMaskIntoConstraints = false
2022-11-30 16:56:59 +01:00
2026-03-08 19:06:08 +01:00
self . addSubview ( self . background , positioned : . below , relativeTo : . none )
2022-12-02 17:04:07 +01:00
self . addSubview ( self . container )
2026-03-06 20:49:37 +01:00
NSLayoutConstraint . activate ( [
2026-03-08 19:06:08 +01:00
self . background . leadingAnchor . constraint ( equalTo : leadingAnchor ) ,
self . background . trailingAnchor . constraint ( equalTo : trailingAnchor ) ,
self . background . topAnchor . constraint ( equalTo : topAnchor ) ,
self . background . bottomAnchor . constraint ( equalTo : bottomAnchor ) ,
2026-03-06 20:49:37 +01:00
self . container . leadingAnchor . constraint ( equalTo : leadingAnchor ) ,
self . container . trailingAnchor . constraint ( equalTo : trailingAnchor ) ,
self . container . topAnchor . constraint ( equalTo : topAnchor , constant : Constants . Popup . headerHeight * 1.4 ) ,
self . container . bottomAnchor . constraint ( equalTo : bottomAnchor )
] )
2022-11-30 16:56:59 +01:00
}
2020-11-13 16:29:44 +01:00
2022-11-30 16:56:59 +01:00
required init ? ( coder : NSCoder ) {
fatalError ( " init(coder:) has not been implemented " )
}
2020-06-07 12:22:32 +02:00
2024-05-08 17:21:58 +02:00
fileprivate func setView ( _ view : NSView ) {
2022-12-02 17:04:07 +01:00
self . container . subviews . forEach { $0 . removeFromSuperview ( ) }
self . container . addArrangedSubview ( view )
NSLayoutConstraint . activate ( [
view . leftAnchor . constraint ( equalTo : self . container . leftAnchor ) ,
view . rightAnchor . constraint ( equalTo : self . container . rightAnchor ) ,
view . topAnchor . constraint ( equalTo : self . container . topAnchor ) ,
view . bottomAnchor . constraint ( equalTo : self . container . bottomAnchor )
] )
2022-11-30 16:56:59 +01:00
}
}
2022-12-02 17:04:07 +01:00
// MARK: - S i d e b a r
2022-11-30 16:56:59 +01:00
private class SidebarView : NSStackView {
private let scrollView : ScrollableStackView
2020-06-07 12:22:32 +02:00
2021-09-01 23:01:54 +02:00
private let supportPopover = NSPopover ( )
2023-01-20 19:32:26 +01:00
private var pauseButton : NSButton ? = nil
private var pauseState : Bool {
2025-09-23 16:59:26 +02:00
get { Store . shared . bool ( key : " pause " , defaultValue : false ) }
set { Store . shared . set ( key : " pause " , value : newValue ) }
2023-01-20 19:32:26 +01:00
}
2021-09-01 23:01:54 +02:00
2026-02-22 15:52:36 +01:00
private var dashboardIcon : NSImage { NSImage ( systemSymbolName : " circle.grid.3x3.fill " , accessibilityDescription : nil ) ! }
private var settingsIcon : NSImage { iconFromSymbol ( name : " gear " , scale : . large ) }
private var bugIcon : NSImage { iconFromSymbol ( name : " ladybug " , scale : . large ) }
private var supportIcon : NSImage { iconFromSymbol ( name : " heart.fill " , scale : . large ) }
private var pauseIcon : NSImage { iconFromSymbol ( name : " pause.fill " , scale : . large ) }
private var resumeIcon : NSImage { iconFromSymbol ( name : " play.fill " , scale : . large ) }
private var closeIcon : NSImage { iconFromSymbol ( name : " power " , scale : . large ) }
2022-12-24 09:26:04 +01:00
2020-06-07 12:22:32 +02:00
override init ( frame : NSRect ) {
2022-11-30 16:56:59 +01:00
self . scrollView = ScrollableStackView ( frame : NSRect ( x : 0 , y : 0 , width : frame . width , height : frame . height ) )
self . scrollView . stackView . spacing = 0
self . scrollView . stackView . edgeInsets = NSEdgeInsets ( top : 0 , left : 8 , bottom : 0 , right : 8 )
2020-06-07 12:22:32 +02:00
2022-11-30 16:56:59 +01:00
super . init ( frame : frame )
self . orientation = . vertical
self . spacing = 0
self . widthAnchor . constraint ( equalToConstant : frame . width ) . isActive = true
2020-06-07 12:22:32 +02:00
2022-11-30 16:56:59 +01:00
let spacer = NSView ( )
spacer . heightAnchor . constraint ( equalToConstant : 10 ) . isActive = true
2022-12-24 09:26:04 +01:00
self . scrollView . stackView . addArrangedSubview ( MenuItem ( icon : self . dashboardIcon , title : " Dashboard " ) )
2022-11-30 16:56:59 +01:00
self . scrollView . stackView . addArrangedSubview ( spacer )
2020-06-07 12:22:32 +02:00
2021-09-01 23:01:54 +02:00
self . supportPopover . behavior = . transient
self . supportPopover . contentViewController = self . supportView ( )
2023-01-21 14:21:50 +01:00
let additionalButtons : NSStackView = NSStackView ( frame : NSRect ( x : 0 , y : 0 , width : frame . width , height : 45 ) )
additionalButtons . heightAnchor . constraint ( equalToConstant : 45 ) . isActive = true
2022-11-30 16:56:59 +01:00
additionalButtons . orientation = . horizontal
additionalButtons . distribution = . fillEqually
2023-01-21 14:21:50 +01:00
additionalButtons . alignment = . centerY
2022-11-30 16:56:59 +01:00
additionalButtons . spacing = 0
2020-06-07 12:22:32 +02:00
2023-01-20 19:32:26 +01:00
let pauseButton = self . makeButton ( title : localizedString ( " Pause the Stats " ) , image : self . pauseState ? self . resumeIcon : self . pauseIcon , action : #selector ( togglePause ) )
self . pauseButton = pauseButton
2025-09-23 16:59:26 +02:00
additionalButtons . addArrangedSubview ( self . makeButton ( title : localizedString ( " Settings " ) , image : self . settingsIcon , action : #selector ( openSettings ) ) )
2022-12-24 09:26:04 +01:00
additionalButtons . addArrangedSubview ( self . makeButton ( title : localizedString ( " Support the application " ) , image : self . supportIcon , action : #selector ( donate ) ) )
2025-09-23 16:59:26 +02:00
additionalButtons . addArrangedSubview ( self . makeButton ( title : localizedString ( " Report a bug " ) , image : self . bugIcon , action : #selector ( reportBug ) ) )
2023-01-20 19:32:26 +01:00
additionalButtons . addArrangedSubview ( pauseButton )
2022-12-24 09:26:04 +01:00
additionalButtons . addArrangedSubview ( self . makeButton ( title : localizedString ( " Close application " ) , image : self . closeIcon , action : #selector ( closeApp ) ) )
2020-06-07 12:22:32 +02:00
2022-12-18 12:27:24 +01:00
let emptySpace = NSView ( )
emptySpace . heightAnchor . constraint ( equalToConstant : 28 ) . isActive = true
2022-11-30 16:56:59 +01:00
self . addArrangedSubview ( self . scrollView )
self . addArrangedSubview ( additionalButtons )
2023-01-20 19:32:26 +01:00
NotificationCenter . default . addObserver ( self , selector : #selector ( listenForPause ) , name : . pause , object : nil )
}
deinit {
NotificationCenter . default . removeObserver ( self , name : . pause , object : nil )
2020-06-07 12:22:32 +02:00
}
required init ? ( coder : NSCoder ) {
fatalError ( " init(coder:) has not been implemented " )
}
2024-05-08 17:21:58 +02:00
fileprivate func openMenu ( _ title : String ) {
2022-11-30 16:56:59 +01:00
self . scrollView . stackView . subviews . forEach ( { ( m : NSView ) in
if let menu = m as ? MenuItem {
2020-06-07 12:22:32 +02:00
if menu . title = = title {
menu . activate ( )
2022-11-30 16:56:59 +01:00
} else {
menu . reset ( )
2020-06-07 12:22:32 +02:00
}
}
} )
}
2024-05-08 17:21:58 +02:00
fileprivate func setModules ( _ list : [ Module ] ) {
2022-11-30 16:56:59 +01:00
list . reversed ( ) . forEach { ( m : Module ) in
2020-06-07 12:22:32 +02:00
if ! m . available { return }
2022-11-30 16:56:59 +01:00
let menu : NSView = MenuItem ( icon : m . config . icon , title : m . config . name )
self . scrollView . stackView . insertArrangedSubview ( menu , at : 2 )
2020-06-07 12:22:32 +02:00
}
}
2022-12-24 09:26:04 +01:00
private func makeButton ( title : String , image : NSImage , action : Selector ) -> NSButton {
2023-01-21 14:21:50 +01:00
let button = NSButton ( )
2020-06-07 12:22:32 +02:00
button . title = title
2020-07-11 20:25:03 +02:00
button . toolTip = title
2020-06-07 12:22:32 +02:00
button . bezelStyle = . regularSquare
button . translatesAutoresizingMaskIntoConstraints = false
button . imageScaling = . scaleNone
2022-12-24 09:26:04 +01:00
button . image = image
2023-02-16 19:44:10 +01:00
button . contentTintColor = . secondaryLabelColor
2020-06-07 12:22:32 +02:00
button . isBordered = false
button . action = action
button . target = self
button . focusRingType = . none
2025-09-23 16:59:26 +02:00
button . widthAnchor . constraint ( equalToConstant : 33 ) . isActive = true
2020-06-07 12:22:32 +02:00
2025-09-23 16:59:26 +02:00
let rect = NSRect ( x : 0 , y : 0 , width : 33 , height : 45 )
2021-05-22 14:58:20 +02:00
let trackingArea = NSTrackingArea (
rect : rect ,
options : [ NSTrackingArea . Options . activeAlways , NSTrackingArea . Options . mouseEnteredAndExited , NSTrackingArea . Options . activeInActiveApp ] ,
owner : self ,
userInfo : [ " button " : title ]
)
2020-06-07 12:22:32 +02:00
self . addTrackingArea ( trackingArea )
return button
}
2021-09-01 23:01:54 +02:00
private func supportView ( ) -> NSViewController {
let vc : NSViewController = NSViewController ( nibName : nil , bundle : nil )
2025-09-23 16:59:26 +02:00
let view : NSStackView = NSStackView ( frame : NSRect ( x : 0 , y : 0 , width : 180 , height : 54 ) )
view . spacing = 10
view . edgeInsets = NSEdgeInsets ( top : 0 , left : 15 , bottom : 0 , right : 0 )
2021-09-01 23:01:54 +02:00
view . orientation = . horizontal
2023-11-22 18:17:07 +01:00
let github = SupportButtonView ( name : " GitHub Sponsors " , image : " github " , action : {
NSWorkspace . shared . open ( URL ( string : " https://github.com/sponsors/exelban " ) ! )
} )
let paypal = SupportButtonView ( name : " PayPal " , image : " paypal " , action : {
NSWorkspace . shared . open ( URL ( string : " https://www.paypal.com/donate?hosted_button_id=3DS5JHDBATMTC " ) ! )
} )
let koFi = SupportButtonView ( name : " Ko-fi " , image : " ko-fi " , action : {
NSWorkspace . shared . open ( URL ( string : " https://ko-fi.com/exelban " ) ! )
} )
let patreon = SupportButtonView ( name : " Patreon " , image : " patreon " , action : {
NSWorkspace . shared . open ( URL ( string : " https://patreon.com/exelban " ) ! )
} )
view . addArrangedSubview ( github )
view . addArrangedSubview ( paypal )
view . addArrangedSubview ( koFi )
view . addArrangedSubview ( patreon )
2021-09-01 23:01:54 +02:00
vc . view = view
return vc
}
2025-09-23 16:59:26 +02:00
@objc private func openSettings ( ) {
NotificationCenter . default . post ( name : . openModuleSettings , object : nil , userInfo : [ " module " : " Settings " ] )
}
2024-05-08 17:21:58 +02:00
@objc private func reportBug ( ) {
2025-10-07 18:00:01 +02:00
NSWorkspace . shared . open ( URL ( string : " https://github.com/exelban/stats/issues/new?template=bug_report.md " ) ! )
2020-06-07 12:22:32 +02:00
}
2021-09-01 23:01:54 +02:00
@objc private func donate ( _ sender : NSButton ) {
self . supportPopover . show ( relativeTo : sender . bounds , of : sender , preferredEdge : NSRectEdge . minY )
}
2024-05-08 17:21:58 +02:00
@objc private func closeApp ( _ sender : NSButton ) {
2020-06-07 12:22:32 +02:00
NSApp . terminate ( sender )
}
2023-01-20 19:32:26 +01:00
2024-05-08 17:21:58 +02:00
@objc private func togglePause ( ) {
2023-01-20 19:32:26 +01:00
self . pauseState = ! self . pauseState
self . pauseButton ? . toolTip = localizedString ( self . pauseState ? " Resume the Stats " : " Pause the Stats " )
self . pauseButton ? . image = self . pauseState ? self . resumeIcon : self . pauseIcon
NotificationCenter . default . post ( name : . pause , object : nil , userInfo : [ " state " : self . pauseState ] )
}
@objc func listenForPause ( ) {
self . pauseButton ? . toolTip = localizedString ( self . pauseState ? " Resume the Stats " : " Pause the Stats " )
self . pauseButton ? . image = self . pauseState ? self . resumeIcon : self . pauseIcon
}
2020-06-07 12:22:32 +02:00
}
2022-11-30 16:56:59 +01:00
private class MenuItem : NSView {
2024-05-08 17:21:58 +02:00
fileprivate let title : String
2020-06-07 12:22:32 +02:00
2024-05-08 17:21:58 +02:00
private var active : Bool = false
2020-06-07 12:22:32 +02:00
private var imageView : NSImageView ? = nil
private var titleView : NSTextField ? = nil
2022-11-30 16:56:59 +01:00
init ( icon : NSImage ? , title : String ) {
2020-06-07 12:22:32 +02:00
self . title = title
2022-11-30 16:56:59 +01:00
super . init ( frame : NSRect . zero )
2020-06-07 12:22:32 +02:00
self . wantsLayer = true
2022-11-30 16:56:59 +01:00
self . layer ? . cornerRadius = 5
2021-07-01 21:22:59 +02:00
var toolTip = " "
2022-11-30 16:56:59 +01:00
if title = = " Settings " {
2021-07-01 21:22:59 +02:00
toolTip = localizedString ( " Open application settings " )
} else if title = = " Dashboard " {
toolTip = localizedString ( " Open dashboard " )
} else {
toolTip = localizedString ( " Open \( title ) settings " )
}
self . toolTip = toolTip
2020-06-07 12:22:32 +02:00
let imageView = NSImageView ( )
if icon != nil {
imageView . image = icon !
}
2022-11-30 16:56:59 +01:00
imageView . frame = NSRect ( x : 8 , y : ( 32 - 18 ) / 2 , width : 18 , height : 18 )
2020-06-07 12:22:32 +02:00
imageView . wantsLayer = true
2023-02-13 18:28:00 +01:00
imageView . contentTintColor = . labelColor
2022-11-30 16:56:59 +01:00
self . imageView = imageView
2020-06-07 12:22:32 +02:00
2022-11-30 16:56:59 +01:00
let titleView = TextView ( frame : NSRect ( x : 34 , y : ( ( 32 - 16 ) / 2 ) + 1 , width : 100 , height : 16 ) )
2020-11-13 16:29:44 +01:00
titleView . textColor = . labelColor
2020-11-13 18:27:04 +01:00
titleView . font = NSFont . systemFont ( ofSize : 13 , weight : . regular )
2021-07-01 21:22:59 +02:00
titleView . stringValue = localizedString ( title )
2022-11-30 16:56:59 +01:00
self . titleView = titleView
2020-06-07 12:22:32 +02:00
self . addSubview ( imageView )
self . addSubview ( titleView )
2022-11-30 16:56:59 +01:00
NSLayoutConstraint . activate ( [
self . heightAnchor . constraint ( equalToConstant : 32 )
] )
2020-06-07 12:22:32 +02:00
}
required init ? ( coder : NSCoder ) {
fatalError ( " init(coder:) has not been implemented " )
}
override func mouseDown ( with : NSEvent ) {
self . activate ( )
}
2024-05-08 17:21:58 +02:00
fileprivate func activate ( ) {
2022-11-30 16:56:59 +01:00
guard ! self . active else { return }
2020-06-07 12:22:32 +02:00
self . active = true
2022-11-30 16:56:59 +01:00
NotificationCenter . default . post ( name : . openModuleSettings , object : nil , userInfo : [ " module " : self . title ] )
2023-01-31 18:27:43 +01:00
self . layer ? . backgroundColor = NSColor . selectedContentBackgroundColor . cgColor
self . imageView ? . contentTintColor = . white
2022-11-30 16:56:59 +01:00
self . titleView ? . textColor = . white
2020-06-07 12:22:32 +02:00
}
2020-11-13 18:27:04 +01:00
2024-05-08 17:21:58 +02:00
fileprivate func reset ( ) {
2020-11-13 18:27:04 +01:00
self . layer ? . backgroundColor = . clear
2023-01-31 18:27:43 +01:00
self . imageView ? . contentTintColor = . labelColor
2022-11-30 16:56:59 +01:00
self . titleView ? . textColor = . labelColor
2020-11-13 18:27:04 +01:00
self . active = false
}
2020-06-07 12:22:32 +02:00
}
2026-03-14 20:25:39 +01:00
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 " )
}
}
}