iOS/Sources/MacBridge/MacBridgeStatusItem.swift

129 lines
4.0 KiB
Swift

import AppKit
class MacBridgeStatusItem: NSObject, NSMenuDelegate {
private let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
private var lastConfiguration: MacBridgeStatusItemConfiguration?
override init() {
super.init()
statusItem.button?.imagePosition = .imageLeading
statusItem.button?.target = self
statusItem.button?.action = #selector(statusItemTapped(_:))
statusItem.button?.sendAction(on: [.leftMouseUp, .leftMouseDown, .rightMouseDown])
}
func configure(title: String) {
statusItem.button?.title = title
}
func configure(using configuration: MacBridgeStatusItemConfiguration) {
lastConfiguration = configuration
statusItem.isVisible = configuration.isVisible
statusItem.button?.setAccessibilityLabel(configuration.accessibilityLabel)
let image = NSImage(cgImage: configuration.image, size: configuration.imageSize)
image.isTemplate = true
statusItem.button?.image = image
}
@objc private func statusItemTapped(_ sender: NSStatusBarButton) {
guard let configuration = lastConfiguration, let event = NSApp.currentEvent else { return }
if event.isRightClickEquivalentEvent {
let mainMenu = menu(for: configuration.items)
mainMenu.delegate = self
statusItem.menu = mainMenu
sender.performClick(sender)
} else if event.type == .leftMouseUp {
// leftMouseDown also fires, but we only want to do that for ctrl-clicks
configuration.primaryActionHandler(MacBridgeStatusItemCallbackInfoImpl(sender: sender))
}
}
func menuDidClose(_ menu: NSMenu) {
statusItem.menu = nil
}
private func modifierKeys(for uglyMask: Int) -> NSEvent.ModifierFlags {
var modifierMask: NSEvent.ModifierFlags = []
let pairings: [(MacBridgeStatusModifierMask, NSEvent.ModifierFlags)] = [
(.capsLock, .capsLock),
(.shift, .shift),
(.control, .control),
(.option, .option),
(.command, .command),
(.numericPad, .numericPad),
(.help, .help),
(.function, .function),
]
for (ugly, good) in pairings where uglyMask & ugly.rawValue != 0 {
modifierMask.insert(good)
}
return modifierMask
}
private func menu(for items: [MacBridgeStatusItemMenuItem]) -> NSMenu {
let menu = NSMenu()
for item in items {
guard !item.isSeparator else {
menu.addItem(.separator())
continue
}
let menuItem = NSMenuItem(
title: item.name,
action: #selector(actionTapped(_:)),
keyEquivalent: item.keyEquivalent
)
menu.addItem(menuItem)
menuItem.keyEquivalentModifierMask = modifierKeys(for: item.keyEquivalentModifierMask)
menuItem.target = self
menuItem.representedObject = item
if let image = item.image, case let imageSize = item.imageSize, imageSize != .zero {
menuItem.image = NSImage(cgImage: image, size: imageSize)
}
if !item.subitems.isEmpty {
menuItem.submenu = self.menu(for: item.subitems)
}
}
return menu
}
@objc private func actionTapped(_ sender: NSMenuItem) {
guard let representedObject = sender.representedObject as? MacBridgeStatusItemMenuItem else { return }
representedObject.primaryActionHandler(MacBridgeStatusItemCallbackInfoImpl(sender: sender))
}
}
class MacBridgeStatusItemCallbackInfoImpl: MacBridgeStatusItemCallbackInfo {
let sender: Any?
init(sender: Any?) {
self.sender = sender
}
var isActive: Bool {
NSApp.isActive
}
func activate() {
NSApp.activate(ignoringOtherApps: true)
}
func deactivate() {
NSApp.hide(sender)
}
func terminate() {
NSApp.terminate(sender)
}
}