Refine macOS menu bar drop target icon

This commit is contained in:
2026-06-09 16:41:10 -04:00
parent 632621b226
commit 1e939f307d

View File

@@ -81,22 +81,28 @@ class AppDelegate: NSObject, NSApplicationDelegate {
func updateIcon(config: SyncConfig) {
guard controller.uploadProgress == nil else { return }
let iconName = config.iconName
let image = NSImage(systemSymbolName: iconName, accessibilityDescription: "Cairnquire")
?? NSImage(systemSymbolName: "tray.circle.fill", accessibilityDescription: "Cairnquire")
dropButton.image = image
dropButton.image = makeMenuBarIcon(named: iconName, accessibilityDescription: "Cairnquire")
dropButton.setDropTargetActive(false)
}
private func updateIconForUpload(progress: Double?) {
if progress != nil {
// During upload: show tray.circle (outline)
let image = NSImage(systemSymbolName: "tray.circle", accessibilityDescription: "Uploading")
dropButton.image = image
dropButton.image = makeMenuBarIcon(named: "tray.circle", accessibilityDescription: "Uploading")
dropButton.setDropTargetActive(false)
} else {
// Upload complete: restore normal icon
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() {
guard let button = dropButton else { return }
@@ -325,19 +331,32 @@ class DropStatusButton: NSButton {
}
private func setup() {
isBordered = false
bezelStyle = .regularSquare
imageScaling = .scaleProportionallyDown
registerForDraggedTypes([.fileURL])
}
func setDropTargetActive(_ isActive: Bool) {
isBordered = isActive
contentTintColor = isActive ? .systemBlue : nil
needsDisplay = true
}
override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
// Tint icon slightly to indicate drop zone is active
if let image = self.image {
self.image = image.tinted(with: .systemBlue)
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
}
override func draggingExited(_ sender: NSDraggingInfo?) {
// Restore original icon
setDropTargetActive(false)
if let appDelegate = appDelegate {
appDelegate.updateIcon(config: appDelegate.controller.config)
}
@@ -347,9 +366,11 @@ class DropStatusButton: NSButton {
let pasteboard = sender.draggingPasteboard
guard let urls = pasteboard.readObjects(forClasses: [NSURL.self], options: nil) as? [URL],
!urls.isEmpty else {
setDropTargetActive(false)
return false
}
setDropTargetActive(false)
appDelegate?.bounceIcon()
Task { @MainActor in
@@ -359,18 +380,3 @@ class DropStatusButton: NSButton {
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
}
}