77
88import Cocoa
99
10- class AppScrubberTouchBarItem : NSCustomTouchBarItem , NSScrubberDelegate , NSScrubberDataSource {
11- private var scrubber : NSScrubber !
12-
13- private var timer : Timer !
14- private var ticks : Int = 0
15- private let minTicks : Int = 5
16- private let maxTicks : Int = 20
17- private var lastSelected : Int = 0
10+ class AppScrubberTouchBarItem : NSCustomTouchBarItem {
11+ private var scrollView = NSScrollView ( )
1812 private var autoResize : Bool = false
1913 private var widthConstraint : NSLayoutConstraint ?
2014
2115 private var persistentAppIdentifiers : [ String ] = [ ]
2216 private var runningAppsIdentifiers : [ String ] = [ ]
2317
2418 private var frontmostApplicationIdentifier : String ? {
25- guard let frontmostId = NSWorkspace . shared. frontmostApplication? . bundleIdentifier else { return nil }
26- return frontmostId
19+ return NSWorkspace . shared. frontmostApplication? . bundleIdentifier
2720 }
2821
2922 private var applications : [ DockItem ] = [ ]
30-
31- convenience override init ( identifier: NSTouchBarItem . Identifier ) {
32- self . init ( identifier: identifier, autoResize: false )
33- }
34-
35- static var iconWidth = 36
36- static var spacingWidth = 2
23+ private var items : [ CustomButtonTouchBarItem ] = [ ]
3724
38- init ( identifier: NSTouchBarItem . Identifier , autoResize: Bool ) {
25+ init ( identifier: NSTouchBarItem . Identifier , autoResize: Bool = false ) {
3926 super. init ( identifier: identifier)
40- self . autoResize = autoResize
41-
42- scrubber = NSScrubber ( )
43- scrubber. delegate = self
44- scrubber. dataSource = self
45- scrubber. mode = . free // .fixed
46- let layout = NSScrubberFlowLayout ( )
47- layout. itemSize = NSSize ( width: AppScrubberTouchBarItem . iconWidth, height: 32 )
48- layout. itemSpacing = CGFloat ( AppScrubberTouchBarItem . spacingWidth)
49- scrubber. scrubberLayout = layout
50- scrubber. selectionBackgroundStyle = . roundedBackground
51- scrubber. showsAdditionalContentIndicators = true
52-
53- view = scrubber
54-
55- scrubber. register ( NSScrubberImageItemView . self, forItemIdentifier: NSUserInterfaceItemIdentifier ( rawValue: " ScrubberApplicationsItemReuseIdentifier " ) )
27+ self . autoResize = autoResize //todo
28+ view = scrollView
5629
5730 NSWorkspace . shared. notificationCenter. addObserver ( self , selector: #selector( activeApplicationChanged) , name: NSWorkspace . didLaunchApplicationNotification, object: nil )
5831 NSWorkspace . shared. notificationCenter. addObserver ( self , selector: #selector( activeApplicationChanged) , name: NSWorkspace . didTerminateApplicationNotification, object: nil )
@@ -75,142 +48,80 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrub
7548 }
7649
7750 func updateRunningApplication( ) {
78- let newApplications = launchedApplications ( )
79-
80- let index = newApplications. firstIndex {
81- $0. bundleIdentifier == frontmostApplicationIdentifier
82- }
83-
84- applications = newApplications
51+ applications = launchedApplications ( )
8552 applications += getDockPersistentAppsList ( )
86- scrubber . reloadData ( )
53+ reloadData ( )
8754 updateSize ( )
88-
89- scrubber. selectedIndex = index ?? 0
9055 }
9156
9257 func updateSize( ) {
9358 if self . autoResize {
94- if let constraint: NSLayoutConstraint = self . widthConstraint {
95- constraint. isActive = false
96- self . scrubber. removeConstraint ( constraint)
97- }
98- let width = ( AppScrubberTouchBarItem . iconWidth + AppScrubberTouchBarItem. spacingWidth) * self . applications. count - AppScrubberTouchBarItem. spacingWidth
99- self . widthConstraint = self . scrubber. widthAnchor. constraint ( equalToConstant: CGFloat ( width) )
59+ self . widthConstraint? . isActive = false
60+
61+ let width = self . scrollView. documentView? . fittingSize. width ?? 0
62+ self . widthConstraint = self . scrollView. widthAnchor. constraint ( equalToConstant: width)
10063 self . widthConstraint!. isActive = true
10164 }
10265 }
103-
104- public func numberOfItems( for _: NSScrubber ) -> Int {
105- return applications. count
106- }
107-
108- public func scrubber( _ scrubber: NSScrubber , viewForItemAt index: Int ) -> NSScrubberItemView {
109- let item = scrubber. makeItem ( withIdentifier: NSUserInterfaceItemIdentifier ( rawValue: " ScrubberApplicationsItemReuseIdentifier " ) , owner: self ) as? NSScrubberImageItemView ?? NSScrubberImageItemView ( )
110- item. imageView. imageScaling = . scaleProportionallyDown
111-
112- let app = applications [ index]
113-
114- if let icon = app. icon {
115- item. image = icon
116- item. image. size = NSSize ( width: 26 , height: 26 )
66+
67+ func reloadData( ) {
68+ let frontMostAppId = self . frontmostApplicationIdentifier
69+ items = applications. map { self . createAppButton ( for: $0, isFrontmost: $0. bundleIdentifier == frontMostAppId) }
70+ let stackView = NSStackView ( views: items. compactMap { $0. view } )
71+ stackView. spacing = 1
72+ stackView. orientation = . horizontal
73+ let visibleRect = self . scrollView. documentVisibleRect
74+ scrollView. documentView = stackView
75+ stackView. scroll ( visibleRect. origin)
76+ }
77+
78+ public func createAppButton( for app: DockItem , isFrontmost: Bool ) -> CustomButtonTouchBarItem {
79+ let item = DockBarItem ( app, isRunning: runningAppsIdentifiers. contains ( app. bundleIdentifier) , isFrontmost: isFrontmost)
80+ item. isBordered = false
81+ item. tapClosure = { [ weak self] in
82+ self ? . switchToApp ( app: app)
11783 }
118-
119- item. removeFromSuperview ( )
120-
121- let dotView = NSView ( frame: . zero)
122- dotView. wantsLayer = true
123- if runningAppsIdentifiers. contains ( app. bundleIdentifier!) {
124- dotView. layer? . backgroundColor = NSColor . white. cgColor
125- } else {
126- dotView. layer? . backgroundColor = NSColor . black. cgColor
84+ item. longTapClosure = { [ weak self] in
85+ self ? . handleHalfLongPress ( item: app)
12786 }
128- dotView. layer? . cornerRadius = 1.5
129- dotView. setFrameOrigin ( NSPoint ( x: 17 , y: 1 ) )
130- dotView. frame. size = NSSize ( width: 3 , height: 3 )
131- item. addSubview ( dotView)
132-
87+
13388 return item
13489 }
135-
136- public func didBeginInteracting( with _: NSScrubber ) {
137- stopTimer ( )
138- ticks = 0
139- timer = Timer . scheduledTimer ( timeInterval: 0.1 , target: self , selector: #selector( checkTimer) , userInfo: nil , repeats: true )
140- }
141-
142- @objc private func checkTimer( ) {
143- ticks += 1
144-
145- if ticks == minTicks {
146- HapticFeedback . shared? . tap ( strong: 2 )
147- }
148-
149- if ticks > maxTicks {
150- stopTimer ( )
151- HapticFeedback . shared? . tap ( strong: 6 )
152- }
153- }
154-
155- private func stopTimer( ) {
156- timer? . invalidate ( )
157- timer = nil
158- lastSelected = 0
159- }
160-
161- public func didCancelInteracting( with _: NSScrubber ) {
162- stopTimer ( )
163- }
164-
165- public func didFinishInteracting( with scrubber: NSScrubber ) {
166- stopTimer ( )
167-
168- if ticks == 0 {
169- return
170- }
171-
172- if ticks >= minTicks && scrubber. selectedIndex > 0 {
173- longPress ( selected: scrubber. selectedIndex)
174- return
175- }
176-
177- let bundleIdentifier = applications [ scrubber. selectedIndex] . bundleIdentifier
90+
91+ public func switchToApp( app: DockItem ) {
92+ let bundleIdentifier = app. bundleIdentifier
17893 if bundleIdentifier!. contains ( " file:// " ) {
17994 NSWorkspace . shared. openFile ( bundleIdentifier!. replacingOccurrences ( of: " file:// " , with: " " ) )
18095 } else {
18196 NSWorkspace . shared. launchApplication ( withBundleIdentifier: bundleIdentifier!, options: [ . default] , additionalEventParamDescriptor: nil , launchIdentifier: nil )
182- HapticFeedback . shared? . tap ( strong: 6 )
18397 }
18498 updateRunningApplication ( )
18599
186100 // NB: if you can't open app which on another space, try to check mark
187101 // "When switching to an application, switch to a Space with open windows for the application"
188102 // in Mission control settings
189103 }
190-
191- private func longPress( selected: Int ) {
192- let bundleIdentifier = applications [ selected] . bundleIdentifier
193-
194- if ticks > maxTicks {
195- if let processIdentifier = applications [ selected] . pid {
196- if !( NSRunningApplication ( processIdentifier: processIdentifier) ? . terminate ( ) ) ! {
197- NSRunningApplication ( processIdentifier: processIdentifier) ? . forceTerminate ( )
198- }
199- }
200- } else {
201- HapticFeedback . shared? . tap ( strong: 6 )
202- if let index = self . persistentAppIdentifiers. firstIndex ( of: bundleIdentifier!) {
203- persistentAppIdentifiers. remove ( at: index)
204- } else {
205- persistentAppIdentifiers. append ( bundleIdentifier!)
104+
105+ //todo
106+ private func handleLongPress( item: DockItem ) {
107+ if let pid = item. pid, let app = NSRunningApplication ( processIdentifier: pid) {
108+ if !app. terminate ( ) {
109+ app. forceTerminate ( )
206110 }
207-
208- AppSettings . dockPersistentAppIds = persistentAppIdentifiers
111+ updateRunningApplication ( )
209112 }
210- ticks = 0
211- updateRunningApplication ( )
212113 }
114+
115+ private func handleHalfLongPress( item: DockItem ) {
116+ if let index = self . persistentAppIdentifiers. firstIndex ( of: item. bundleIdentifier) {
117+ persistentAppIdentifiers. remove ( at: index)
118+ } else {
119+ persistentAppIdentifiers. append ( item. bundleIdentifier)
120+ }
213121
122+ AppSettings . dockPersistentAppIds = persistentAppIdentifiers
123+ }
124+
214125 private func launchedApplications( ) -> [ DockItem ] {
215126 runningAppsIdentifiers = [ ]
216127 var returnable : [ DockItem ] = [ ]
@@ -220,21 +131,19 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrub
220131
221132 runningAppsIdentifiers. append ( bundleIdentifier)
222133
223- let dockItem = DockItem ( bundleIdentifier: bundleIdentifier, icon: getIcon ( forBundleIdentifier: bundleIdentifier) , pid: app. processIdentifier)
134+ let dockItem = DockItem ( bundleIdentifier: bundleIdentifier, icon: app . icon ?? getIcon ( forBundleIdentifier: bundleIdentifier) , pid: app. processIdentifier)
224135 returnable. append ( dockItem)
225136 }
226137 return returnable
227138 }
228139
229- public func getIcon( forBundleIdentifier bundleIdentifier: String ? = nil , orPath path: String ? = nil , orType _: String ? = nil ) -> NSImage {
230- if bundleIdentifier != nil {
231- if let appPath = NSWorkspace . shared. absolutePathForApplication ( withBundleIdentifier: bundleIdentifier!) {
232- return NSWorkspace . shared. icon ( forFile: appPath)
233- }
140+ public func getIcon( forBundleIdentifier bundleIdentifier: String ? = nil , orPath path: String ? = nil ) -> NSImage {
141+ if let bundleIdentifier = bundleIdentifier, let appPath = NSWorkspace . shared. absolutePathForApplication ( withBundleIdentifier: bundleIdentifier) {
142+ return NSWorkspace . shared. icon ( forFile: appPath)
234143 }
235144
236- if path != nil {
237- return NSWorkspace . shared. icon ( forFile: path! )
145+ if let path = path {
146+ return NSWorkspace . shared. icon ( forFile: path)
238147 }
239148
240149 let genericIcon = NSImage ( contentsOfFile: " /System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericDocumentIcon.icns " )
@@ -265,3 +174,29 @@ public class DockItem: NSObject {
265174 self . pid = pid
266175 }
267176}
177+
178+ private let iconWidth = 32.0
179+ class DockBarItem : CustomButtonTouchBarItem {
180+
181+ init ( _ app: DockItem , isRunning: Bool , isFrontmost: Bool ) {
182+ super. init ( identifier: . init( app. bundleIdentifier) , title: " " )
183+
184+ image = app. icon
185+ image? . size = NSSize ( width: iconWidth, height: iconWidth)
186+
187+ let dotColor : NSColor = isRunning ? . white : . black
188+ self . finishViewConfiguration = { [ weak self] in
189+ let dotView = NSView ( frame: . zero)
190+ dotView. wantsLayer = true
191+ dotView. layer? . backgroundColor = dotColor. cgColor
192+ dotView. layer? . cornerRadius = 1.5
193+ dotView. frame. size = NSSize ( width: isFrontmost ? iconWidth - 14 : 3 , height: 3 )
194+ self ? . view. addSubview ( dotView)
195+ dotView. setFrameOrigin ( NSPoint ( x: 18.0 - Double( dotView. frame. size. width) / 2.0 , y: iconWidth - 5 ) )
196+ }
197+ }
198+
199+ required init ? ( coder _: NSCoder ) {
200+ fatalError ( " init(coder:) has not been implemented " )
201+ }
202+ }
0 commit comments