Refine macOS menu bar drop target icon
This commit is contained in:
@@ -81,22 +81,28 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
func updateIcon(config: SyncConfig) {
|
func updateIcon(config: SyncConfig) {
|
||||||
guard controller.uploadProgress == nil else { return }
|
guard controller.uploadProgress == nil else { return }
|
||||||
let iconName = config.iconName
|
let iconName = config.iconName
|
||||||
let image = NSImage(systemSymbolName: iconName, accessibilityDescription: "Cairnquire")
|
dropButton.image = makeMenuBarIcon(named: iconName, accessibilityDescription: "Cairnquire")
|
||||||
?? NSImage(systemSymbolName: "tray.circle.fill", accessibilityDescription: "Cairnquire")
|
dropButton.setDropTargetActive(false)
|
||||||
dropButton.image = image
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateIconForUpload(progress: Double?) {
|
private func updateIconForUpload(progress: Double?) {
|
||||||
if progress != nil {
|
if progress != nil {
|
||||||
// During upload: show tray.circle (outline)
|
// During upload: show tray.circle (outline)
|
||||||
let image = NSImage(systemSymbolName: "tray.circle", accessibilityDescription: "Uploading")
|
dropButton.image = makeMenuBarIcon(named: "tray.circle", accessibilityDescription: "Uploading")
|
||||||
dropButton.image = image
|
dropButton.setDropTargetActive(false)
|
||||||
} else {
|
} else {
|
||||||
// Upload complete: restore normal icon
|
// Upload complete: restore normal icon
|
||||||
updateIcon(config: controller.config)
|
updateIcon(config: controller.config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func makeMenuBarIcon(named iconName: String, accessibilityDescription: String) -> NSImage? {
|
||||||
|
let image = NSImage(systemSymbolName: iconName, accessibilityDescription: accessibilityDescription)
|
||||||
|
?? NSImage(systemSymbolName: "tray.circle.fill", accessibilityDescription: accessibilityDescription)
|
||||||
|
image?.isTemplate = true
|
||||||
|
return image
|
||||||
|
}
|
||||||
|
|
||||||
func bounceIcon() {
|
func bounceIcon() {
|
||||||
guard let button = dropButton else { return }
|
guard let button = dropButton else { return }
|
||||||
|
|
||||||
@@ -325,19 +331,32 @@ class DropStatusButton: NSButton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func setup() {
|
private func setup() {
|
||||||
|
isBordered = false
|
||||||
|
bezelStyle = .regularSquare
|
||||||
|
imageScaling = .scaleProportionallyDown
|
||||||
registerForDraggedTypes([.fileURL])
|
registerForDraggedTypes([.fileURL])
|
||||||
}
|
}
|
||||||
|
|
||||||
override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
|
func setDropTargetActive(_ isActive: Bool) {
|
||||||
// Tint icon slightly to indicate drop zone is active
|
isBordered = isActive
|
||||||
if let image = self.image {
|
contentTintColor = isActive ? .systemBlue : nil
|
||||||
self.image = image.tinted(with: .systemBlue)
|
needsDisplay = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
|
||||||
|
let pasteboard = sender.draggingPasteboard
|
||||||
|
guard pasteboard.canReadObject(forClasses: [NSURL.self], options: nil) else {
|
||||||
|
setDropTargetActive(false)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the button frame only while the menu bar icon is a valid drop target.
|
||||||
|
setDropTargetActive(true)
|
||||||
return .copy
|
return .copy
|
||||||
}
|
}
|
||||||
|
|
||||||
override func draggingExited(_ sender: NSDraggingInfo?) {
|
override func draggingExited(_ sender: NSDraggingInfo?) {
|
||||||
// Restore original icon
|
setDropTargetActive(false)
|
||||||
if let appDelegate = appDelegate {
|
if let appDelegate = appDelegate {
|
||||||
appDelegate.updateIcon(config: appDelegate.controller.config)
|
appDelegate.updateIcon(config: appDelegate.controller.config)
|
||||||
}
|
}
|
||||||
@@ -347,9 +366,11 @@ class DropStatusButton: NSButton {
|
|||||||
let pasteboard = sender.draggingPasteboard
|
let pasteboard = sender.draggingPasteboard
|
||||||
guard let urls = pasteboard.readObjects(forClasses: [NSURL.self], options: nil) as? [URL],
|
guard let urls = pasteboard.readObjects(forClasses: [NSURL.self], options: nil) as? [URL],
|
||||||
!urls.isEmpty else {
|
!urls.isEmpty else {
|
||||||
|
setDropTargetActive(false)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setDropTargetActive(false)
|
||||||
appDelegate?.bounceIcon()
|
appDelegate?.bounceIcon()
|
||||||
|
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
@@ -359,18 +380,3 @@ class DropStatusButton: NSButton {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NSImage {
|
|
||||||
func tinted(with color: NSColor) -> NSImage {
|
|
||||||
let image = self.copy() as! NSImage
|
|
||||||
image.isTemplate = false
|
|
||||||
|
|
||||||
let rect = NSRect(origin: .zero, size: image.size)
|
|
||||||
image.lockFocus()
|
|
||||||
color.set()
|
|
||||||
rect.fill(using: .sourceAtop)
|
|
||||||
image.unlockFocus()
|
|
||||||
|
|
||||||
return image
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user