Skip to content

Commit f82d769

Browse files
committed
rewrite in scrollview; get rid of custom gesture handling
1 parent 94717a5 commit f82d769

File tree

2 files changed

+87
-150
lines changed

2 files changed

+87
-150
lines changed

MTMR/CustomButtonTouchBarItem.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat
1515
longClick.isEnabled = longTapClosure != nil
1616
}
1717
}
18+
var finishViewConfiguration: ()->() = {}
1819

1920
private var button: NSButton!
2021
private var singleClick: HapticClickGestureRecognizer!
@@ -100,6 +101,7 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat
100101

101102
view.addGestureRecognizer(longClick)
102103
view.addGestureRecognizer(singleClick)
104+
finishViewConfiguration()
103105
}
104106

105107
func gestureRecognizer(_ gestureRecognizer: NSGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: NSGestureRecognizer) -> Bool {

MTMR/Widgets/AppScrubberTouchBarItem.swift

Lines changed: 85 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -7,52 +7,25 @@
77

88
import 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

Comments
 (0)