From f82d7694eb5e4ce266a418757b502032968563a8 Mon Sep 17 00:00:00 2001 From: Serg Date: Sat, 26 Oct 2019 21:23:50 +0700 Subject: [PATCH] rewrite in scrollview; get rid of custom gesture handling --- MTMR/CustomButtonTouchBarItem.swift | 2 + MTMR/Widgets/AppScrubberTouchBarItem.swift | 231 ++++++++------------- 2 files changed, 85 insertions(+), 148 deletions(-) diff --git a/MTMR/CustomButtonTouchBarItem.swift b/MTMR/CustomButtonTouchBarItem.swift index 5ae98de..e56356f 100644 --- a/MTMR/CustomButtonTouchBarItem.swift +++ b/MTMR/CustomButtonTouchBarItem.swift @@ -15,6 +15,7 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat longClick.isEnabled = longTapClosure != nil } } + var finishViewConfiguration: ()->() = {} private var button: NSButton! private var singleClick: HapticClickGestureRecognizer! @@ -100,6 +101,7 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat view.addGestureRecognizer(longClick) view.addGestureRecognizer(singleClick) + finishViewConfiguration() } func gestureRecognizer(_ gestureRecognizer: NSGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: NSGestureRecognizer) -> Bool { diff --git a/MTMR/Widgets/AppScrubberTouchBarItem.swift b/MTMR/Widgets/AppScrubberTouchBarItem.swift index b5c4d13..8428bb4 100644 --- a/MTMR/Widgets/AppScrubberTouchBarItem.swift +++ b/MTMR/Widgets/AppScrubberTouchBarItem.swift @@ -7,14 +7,8 @@ import Cocoa -class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrubberDataSource { - private var scrubber: NSScrubber! - - private var timer: Timer! - private var ticks: Int = 0 - private let minTicks: Int = 5 - private let maxTicks: Int = 20 - private var lastSelected: Int = 0 +class AppScrubberTouchBarItem: NSCustomTouchBarItem { + private var scrollView = NSScrollView() private var autoResize: Bool = false private var widthConstraint: NSLayoutConstraint? @@ -22,37 +16,16 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrub private var runningAppsIdentifiers: [String] = [] private var frontmostApplicationIdentifier: String? { - guard let frontmostId = NSWorkspace.shared.frontmostApplication?.bundleIdentifier else { return nil } - return frontmostId + return NSWorkspace.shared.frontmostApplication?.bundleIdentifier } private var applications: [DockItem] = [] - - convenience override init(identifier: NSTouchBarItem.Identifier) { - self.init(identifier: identifier, autoResize: false) - } - - static var iconWidth = 36 - static var spacingWidth = 2 + private var items: [CustomButtonTouchBarItem] = [] - init(identifier: NSTouchBarItem.Identifier, autoResize: Bool) { + init(identifier: NSTouchBarItem.Identifier, autoResize: Bool = false) { super.init(identifier: identifier) - self.autoResize = autoResize - - scrubber = NSScrubber() - scrubber.delegate = self - scrubber.dataSource = self - scrubber.mode = .free // .fixed - let layout = NSScrubberFlowLayout() - layout.itemSize = NSSize(width: AppScrubberTouchBarItem.iconWidth, height: 32) - layout.itemSpacing = CGFloat(AppScrubberTouchBarItem.spacingWidth) - scrubber.scrubberLayout = layout - scrubber.selectionBackgroundStyle = .roundedBackground - scrubber.showsAdditionalContentIndicators = true - - view = scrubber - - scrubber.register(NSScrubberImageItemView.self, forItemIdentifier: NSUserInterfaceItemIdentifier(rawValue: "ScrubberApplicationsItemReuseIdentifier")) + self.autoResize = autoResize //todo + view = scrollView NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didLaunchApplicationNotification, object: nil) NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didTerminateApplicationNotification, object: nil) @@ -75,111 +48,52 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrub } func updateRunningApplication() { - let newApplications = launchedApplications() - - let index = newApplications.firstIndex { - $0.bundleIdentifier == frontmostApplicationIdentifier - } - - applications = newApplications + applications = launchedApplications() applications += getDockPersistentAppsList() - scrubber.reloadData() + reloadData() updateSize() - - scrubber.selectedIndex = index ?? 0 } func updateSize() { if self.autoResize { - if let constraint: NSLayoutConstraint = self.widthConstraint { - constraint.isActive = false - self.scrubber.removeConstraint(constraint) - } - let width = (AppScrubberTouchBarItem.iconWidth + AppScrubberTouchBarItem.spacingWidth) * self.applications.count - AppScrubberTouchBarItem.spacingWidth - self.widthConstraint = self.scrubber.widthAnchor.constraint(equalToConstant: CGFloat(width)) + self.widthConstraint?.isActive = false + + let width = self.scrollView.documentView?.fittingSize.width ?? 0 + self.widthConstraint = self.scrollView.widthAnchor.constraint(equalToConstant: width) self.widthConstraint!.isActive = true } } - - public func numberOfItems(for _: NSScrubber) -> Int { - return applications.count + + func reloadData() { + let frontMostAppId = self.frontmostApplicationIdentifier + items = applications.map { self.createAppButton(for: $0, isFrontmost: $0.bundleIdentifier == frontMostAppId) } + let stackView = NSStackView(views: items.compactMap { $0.view }) + stackView.spacing = 1 + stackView.orientation = .horizontal + let visibleRect = self.scrollView.documentVisibleRect + scrollView.documentView = stackView + stackView.scroll(visibleRect.origin) } - public func scrubber(_ scrubber: NSScrubber, viewForItemAt index: Int) -> NSScrubberItemView { - let item = scrubber.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "ScrubberApplicationsItemReuseIdentifier"), owner: self) as? NSScrubberImageItemView ?? NSScrubberImageItemView() - item.imageView.imageScaling = .scaleProportionallyDown - - let app = applications[index] - - if let icon = app.icon { - item.image = icon - item.image.size = NSSize(width: 26, height: 26) + public func createAppButton(for app: DockItem, isFrontmost: Bool) -> CustomButtonTouchBarItem { + let item = DockBarItem(app, isRunning: runningAppsIdentifiers.contains(app.bundleIdentifier), isFrontmost: isFrontmost) + item.isBordered = false + item.tapClosure = { [weak self] in + self?.switchToApp(app: app) } - - item.removeFromSuperview() - - let dotView = NSView(frame: .zero) - dotView.wantsLayer = true - if runningAppsIdentifiers.contains(app.bundleIdentifier!) { - dotView.layer?.backgroundColor = NSColor.white.cgColor - } else { - dotView.layer?.backgroundColor = NSColor.black.cgColor + item.longTapClosure = { [weak self] in + self?.handleHalfLongPress(item: app) } - dotView.layer?.cornerRadius = 1.5 - dotView.setFrameOrigin(NSPoint(x: 17, y: 1)) - dotView.frame.size = NSSize(width: 3, height: 3) - item.addSubview(dotView) - + return item } - - public func didBeginInteracting(with _: NSScrubber) { - stopTimer() - ticks = 0 - timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(checkTimer), userInfo: nil, repeats: true) - } - - @objc private func checkTimer() { - ticks += 1 - - if ticks == minTicks { - HapticFeedback.shared?.tap(strong: 2) - } - - if ticks > maxTicks { - stopTimer() - HapticFeedback.shared?.tap(strong: 6) - } - } - - private func stopTimer() { - timer?.invalidate() - timer = nil - lastSelected = 0 - } - - public func didCancelInteracting(with _: NSScrubber) { - stopTimer() - } - - public func didFinishInteracting(with scrubber: NSScrubber) { - stopTimer() - - if ticks == 0 { - return - } - - if ticks >= minTicks && scrubber.selectedIndex > 0 { - longPress(selected: scrubber.selectedIndex) - return - } - - let bundleIdentifier = applications[scrubber.selectedIndex].bundleIdentifier + + public func switchToApp(app: DockItem) { + let bundleIdentifier = app.bundleIdentifier if bundleIdentifier!.contains("file://") { NSWorkspace.shared.openFile(bundleIdentifier!.replacingOccurrences(of: "file://", with: "")) } else { NSWorkspace.shared.launchApplication(withBundleIdentifier: bundleIdentifier!, options: [.default], additionalEventParamDescriptor: nil, launchIdentifier: nil) - HapticFeedback.shared?.tap(strong: 6) } updateRunningApplication() @@ -187,30 +101,27 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrub // "When switching to an application, switch to a Space with open windows for the application" // in Mission control settings } - - private func longPress(selected: Int) { - let bundleIdentifier = applications[selected].bundleIdentifier - - if ticks > maxTicks { - if let processIdentifier = applications[selected].pid { - if !(NSRunningApplication(processIdentifier: processIdentifier)?.terminate())! { - NSRunningApplication(processIdentifier: processIdentifier)?.forceTerminate() - } + + //todo + private func handleLongPress(item: DockItem) { + if let pid = item.pid, let app = NSRunningApplication(processIdentifier: pid) { + if !app.terminate() { + app.forceTerminate() } - } else { - HapticFeedback.shared?.tap(strong: 6) - if let index = self.persistentAppIdentifiers.firstIndex(of: bundleIdentifier!) { - persistentAppIdentifiers.remove(at: index) - } else { - persistentAppIdentifiers.append(bundleIdentifier!) - } - - AppSettings.dockPersistentAppIds = persistentAppIdentifiers + updateRunningApplication() } - ticks = 0 - updateRunningApplication() } + + private func handleHalfLongPress(item: DockItem) { + if let index = self.persistentAppIdentifiers.firstIndex(of: item.bundleIdentifier) { + persistentAppIdentifiers.remove(at: index) + } else { + persistentAppIdentifiers.append(item.bundleIdentifier) + } + AppSettings.dockPersistentAppIds = persistentAppIdentifiers + } + private func launchedApplications() -> [DockItem] { runningAppsIdentifiers = [] var returnable: [DockItem] = [] @@ -220,21 +131,19 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrub runningAppsIdentifiers.append(bundleIdentifier) - let dockItem = DockItem(bundleIdentifier: bundleIdentifier, icon: getIcon(forBundleIdentifier: bundleIdentifier), pid: app.processIdentifier) + let dockItem = DockItem(bundleIdentifier: bundleIdentifier, icon: app.icon ?? getIcon(forBundleIdentifier: bundleIdentifier), pid: app.processIdentifier) returnable.append(dockItem) } return returnable } - public func getIcon(forBundleIdentifier bundleIdentifier: String? = nil, orPath path: String? = nil, orType _: String? = nil) -> NSImage { - if bundleIdentifier != nil { - if let appPath = NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: bundleIdentifier!) { - return NSWorkspace.shared.icon(forFile: appPath) - } + public func getIcon(forBundleIdentifier bundleIdentifier: String? = nil, orPath path: String? = nil) -> NSImage { + if let bundleIdentifier = bundleIdentifier, let appPath = NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: bundleIdentifier) { + return NSWorkspace.shared.icon(forFile: appPath) } - if path != nil { - return NSWorkspace.shared.icon(forFile: path!) + if let path = path { + return NSWorkspace.shared.icon(forFile: path) } let genericIcon = NSImage(contentsOfFile: "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericDocumentIcon.icns") @@ -265,3 +174,29 @@ public class DockItem: NSObject { self.pid = pid } } + +private let iconWidth = 32.0 +class DockBarItem: CustomButtonTouchBarItem { + + init(_ app: DockItem, isRunning: Bool, isFrontmost: Bool) { + super.init(identifier: .init(app.bundleIdentifier), title: "") + + image = app.icon + image?.size = NSSize(width: iconWidth, height: iconWidth) + + let dotColor: NSColor = isRunning ? .white : .black + self.finishViewConfiguration = { [weak self] in + let dotView = NSView(frame: .zero) + dotView.wantsLayer = true + dotView.layer?.backgroundColor = dotColor.cgColor + dotView.layer?.cornerRadius = 1.5 + dotView.frame.size = NSSize(width: isFrontmost ? iconWidth - 14 : 3, height: 3) + self?.view.addSubview(dotView) + dotView.setFrameOrigin(NSPoint(x: 18.0 - Double(dotView.frame.size.width) / 2.0, y: iconWidth - 5)) + } + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +}