mirror of
https://github.com/Toxblh/MTMR.git
synced 2026-01-10 17:08:39 +00:00
rewrite in scrollview; get rid of custom gesture handling
This commit is contained in:
parent
94717a5ea3
commit
f82d7694eb
@ -15,6 +15,7 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat
|
|||||||
longClick.isEnabled = longTapClosure != nil
|
longClick.isEnabled = longTapClosure != nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var finishViewConfiguration: ()->() = {}
|
||||||
|
|
||||||
private var button: NSButton!
|
private var button: NSButton!
|
||||||
private var singleClick: HapticClickGestureRecognizer!
|
private var singleClick: HapticClickGestureRecognizer!
|
||||||
@ -100,6 +101,7 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat
|
|||||||
|
|
||||||
view.addGestureRecognizer(longClick)
|
view.addGestureRecognizer(longClick)
|
||||||
view.addGestureRecognizer(singleClick)
|
view.addGestureRecognizer(singleClick)
|
||||||
|
finishViewConfiguration()
|
||||||
}
|
}
|
||||||
|
|
||||||
func gestureRecognizer(_ gestureRecognizer: NSGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: NSGestureRecognizer) -> Bool {
|
func gestureRecognizer(_ gestureRecognizer: NSGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: NSGestureRecognizer) -> Bool {
|
||||||
|
|||||||
@ -7,14 +7,8 @@
|
|||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrubberDataSource {
|
class AppScrubberTouchBarItem: NSCustomTouchBarItem {
|
||||||
private var scrubber: NSScrubber!
|
private var scrollView = NSScrollView()
|
||||||
|
|
||||||
private var timer: Timer!
|
|
||||||
private var ticks: Int = 0
|
|
||||||
private let minTicks: Int = 5
|
|
||||||
private let maxTicks: Int = 20
|
|
||||||
private var lastSelected: Int = 0
|
|
||||||
private var autoResize: Bool = false
|
private var autoResize: Bool = false
|
||||||
private var widthConstraint: NSLayoutConstraint?
|
private var widthConstraint: NSLayoutConstraint?
|
||||||
|
|
||||||
@ -22,37 +16,16 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrub
|
|||||||
private var runningAppsIdentifiers: [String] = []
|
private var runningAppsIdentifiers: [String] = []
|
||||||
|
|
||||||
private var frontmostApplicationIdentifier: String? {
|
private var frontmostApplicationIdentifier: String? {
|
||||||
guard let frontmostId = NSWorkspace.shared.frontmostApplication?.bundleIdentifier else { return nil }
|
return NSWorkspace.shared.frontmostApplication?.bundleIdentifier
|
||||||
return frontmostId
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var applications: [DockItem] = []
|
private var applications: [DockItem] = []
|
||||||
|
private var items: [CustomButtonTouchBarItem] = []
|
||||||
convenience override init(identifier: NSTouchBarItem.Identifier) {
|
|
||||||
self.init(identifier: identifier, autoResize: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
static var iconWidth = 36
|
|
||||||
static var spacingWidth = 2
|
|
||||||
|
|
||||||
init(identifier: NSTouchBarItem.Identifier, autoResize: Bool) {
|
init(identifier: NSTouchBarItem.Identifier, autoResize: Bool = false) {
|
||||||
super.init(identifier: identifier)
|
super.init(identifier: identifier)
|
||||||
self.autoResize = autoResize
|
self.autoResize = autoResize //todo
|
||||||
|
view = scrollView
|
||||||
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"))
|
|
||||||
|
|
||||||
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didLaunchApplicationNotification, object: nil)
|
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)
|
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didTerminateApplicationNotification, object: nil)
|
||||||
@ -75,111 +48,52 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrub
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateRunningApplication() {
|
func updateRunningApplication() {
|
||||||
let newApplications = launchedApplications()
|
applications = launchedApplications()
|
||||||
|
|
||||||
let index = newApplications.firstIndex {
|
|
||||||
$0.bundleIdentifier == frontmostApplicationIdentifier
|
|
||||||
}
|
|
||||||
|
|
||||||
applications = newApplications
|
|
||||||
applications += getDockPersistentAppsList()
|
applications += getDockPersistentAppsList()
|
||||||
scrubber.reloadData()
|
reloadData()
|
||||||
updateSize()
|
updateSize()
|
||||||
|
|
||||||
scrubber.selectedIndex = index ?? 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateSize() {
|
func updateSize() {
|
||||||
if self.autoResize {
|
if self.autoResize {
|
||||||
if let constraint: NSLayoutConstraint = self.widthConstraint {
|
self.widthConstraint?.isActive = false
|
||||||
constraint.isActive = false
|
|
||||||
self.scrubber.removeConstraint(constraint)
|
let width = self.scrollView.documentView?.fittingSize.width ?? 0
|
||||||
}
|
self.widthConstraint = self.scrollView.widthAnchor.constraint(equalToConstant: width)
|
||||||
let width = (AppScrubberTouchBarItem.iconWidth + AppScrubberTouchBarItem.spacingWidth) * self.applications.count - AppScrubberTouchBarItem.spacingWidth
|
|
||||||
self.widthConstraint = self.scrubber.widthAnchor.constraint(equalToConstant: CGFloat(width))
|
|
||||||
self.widthConstraint!.isActive = true
|
self.widthConstraint!.isActive = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func numberOfItems(for _: NSScrubber) -> Int {
|
func reloadData() {
|
||||||
return applications.count
|
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 {
|
public func createAppButton(for app: DockItem, isFrontmost: Bool) -> CustomButtonTouchBarItem {
|
||||||
let item = scrubber.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "ScrubberApplicationsItemReuseIdentifier"), owner: self) as? NSScrubberImageItemView ?? NSScrubberImageItemView()
|
let item = DockBarItem(app, isRunning: runningAppsIdentifiers.contains(app.bundleIdentifier), isFrontmost: isFrontmost)
|
||||||
item.imageView.imageScaling = .scaleProportionallyDown
|
item.isBordered = false
|
||||||
|
item.tapClosure = { [weak self] in
|
||||||
let app = applications[index]
|
self?.switchToApp(app: app)
|
||||||
|
|
||||||
if let icon = app.icon {
|
|
||||||
item.image = icon
|
|
||||||
item.image.size = NSSize(width: 26, height: 26)
|
|
||||||
}
|
}
|
||||||
|
item.longTapClosure = { [weak self] in
|
||||||
item.removeFromSuperview()
|
self?.handleHalfLongPress(item: app)
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
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
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
public func didBeginInteracting(with _: NSScrubber) {
|
public func switchToApp(app: DockItem) {
|
||||||
stopTimer()
|
let bundleIdentifier = app.bundleIdentifier
|
||||||
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
|
|
||||||
if bundleIdentifier!.contains("file://") {
|
if bundleIdentifier!.contains("file://") {
|
||||||
NSWorkspace.shared.openFile(bundleIdentifier!.replacingOccurrences(of: "file://", with: ""))
|
NSWorkspace.shared.openFile(bundleIdentifier!.replacingOccurrences(of: "file://", with: ""))
|
||||||
} else {
|
} else {
|
||||||
NSWorkspace.shared.launchApplication(withBundleIdentifier: bundleIdentifier!, options: [.default], additionalEventParamDescriptor: nil, launchIdentifier: nil)
|
NSWorkspace.shared.launchApplication(withBundleIdentifier: bundleIdentifier!, options: [.default], additionalEventParamDescriptor: nil, launchIdentifier: nil)
|
||||||
HapticFeedback.shared?.tap(strong: 6)
|
|
||||||
}
|
}
|
||||||
updateRunningApplication()
|
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"
|
// "When switching to an application, switch to a Space with open windows for the application"
|
||||||
// in Mission control settings
|
// in Mission control settings
|
||||||
}
|
}
|
||||||
|
|
||||||
private func longPress(selected: Int) {
|
//todo
|
||||||
let bundleIdentifier = applications[selected].bundleIdentifier
|
private func handleLongPress(item: DockItem) {
|
||||||
|
if let pid = item.pid, let app = NSRunningApplication(processIdentifier: pid) {
|
||||||
if ticks > maxTicks {
|
if !app.terminate() {
|
||||||
if let processIdentifier = applications[selected].pid {
|
app.forceTerminate()
|
||||||
if !(NSRunningApplication(processIdentifier: processIdentifier)?.terminate())! {
|
|
||||||
NSRunningApplication(processIdentifier: processIdentifier)?.forceTerminate()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
updateRunningApplication()
|
||||||
HapticFeedback.shared?.tap(strong: 6)
|
|
||||||
if let index = self.persistentAppIdentifiers.firstIndex(of: bundleIdentifier!) {
|
|
||||||
persistentAppIdentifiers.remove(at: index)
|
|
||||||
} else {
|
|
||||||
persistentAppIdentifiers.append(bundleIdentifier!)
|
|
||||||
}
|
|
||||||
|
|
||||||
AppSettings.dockPersistentAppIds = persistentAppIdentifiers
|
|
||||||
}
|
}
|
||||||
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] {
|
private func launchedApplications() -> [DockItem] {
|
||||||
runningAppsIdentifiers = []
|
runningAppsIdentifiers = []
|
||||||
var returnable: [DockItem] = []
|
var returnable: [DockItem] = []
|
||||||
@ -220,21 +131,19 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrub
|
|||||||
|
|
||||||
runningAppsIdentifiers.append(bundleIdentifier)
|
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)
|
returnable.append(dockItem)
|
||||||
}
|
}
|
||||||
return returnable
|
return returnable
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getIcon(forBundleIdentifier bundleIdentifier: String? = nil, orPath path: String? = nil, orType _: String? = nil) -> NSImage {
|
public func getIcon(forBundleIdentifier bundleIdentifier: String? = nil, orPath path: String? = nil) -> NSImage {
|
||||||
if bundleIdentifier != nil {
|
if let bundleIdentifier = bundleIdentifier, let appPath = NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: bundleIdentifier) {
|
||||||
if let appPath = NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: bundleIdentifier!) {
|
return NSWorkspace.shared.icon(forFile: appPath)
|
||||||
return NSWorkspace.shared.icon(forFile: appPath)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if path != nil {
|
if let path = path {
|
||||||
return NSWorkspace.shared.icon(forFile: path!)
|
return NSWorkspace.shared.icon(forFile: path)
|
||||||
}
|
}
|
||||||
|
|
||||||
let genericIcon = NSImage(contentsOfFile: "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericDocumentIcon.icns")
|
let genericIcon = NSImage(contentsOfFile: "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericDocumentIcon.icns")
|
||||||
@ -265,3 +174,29 @@ public class DockItem: NSObject {
|
|||||||
self.pid = pid
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user