Skip to content

Commit

Permalink
rewrite in scrollview; get rid of custom gesture handling
Browse files Browse the repository at this point in the history
  • Loading branch information
ReDetection committed Oct 26, 2019
1 parent 94717a5 commit f82d769
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 150 deletions.
2 changes: 2 additions & 0 deletions MTMR/CustomButtonTouchBarItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat
longClick.isEnabled = longTapClosure != nil
}
}
var finishViewConfiguration: ()->() = {}

private var button: NSButton!
private var singleClick: HapticClickGestureRecognizer!
Expand Down Expand Up @@ -100,6 +101,7 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat

view.addGestureRecognizer(longClick)
view.addGestureRecognizer(singleClick)
finishViewConfiguration()
}

func gestureRecognizer(_ gestureRecognizer: NSGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: NSGestureRecognizer) -> Bool {
Expand Down
235 changes: 85 additions & 150 deletions MTMR/Widgets/AppScrubberTouchBarItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,52 +7,25 @@

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?

private var persistentAppIdentifiers: [String] = []
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)
Expand All @@ -75,142 +48,80 @@ 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
}

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)

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 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()

// NB: if you can't open app which on another space, try to check mark
// "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()
}
}
} else {
HapticFeedback.shared?.tap(strong: 6)
if let index = self.persistentAppIdentifiers.firstIndex(of: bundleIdentifier!) {
persistentAppIdentifiers.remove(at: index)
} else {
persistentAppIdentifiers.append(bundleIdentifier!)

//todo
private func handleLongPress(item: DockItem) {
if let pid = item.pid, let app = NSRunningApplication(processIdentifier: pid) {
if !app.terminate() {
app.forceTerminate()
}

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] = []
Expand All @@ -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")
Expand Down Expand Up @@ -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")
}
}

0 comments on commit f82d769

Please sign in to comment.