108 lines
3.6 KiB
Swift
108 lines
3.6 KiB
Swift
import AppKit
|
|
|
|
enum StatusItemMenuProviderNavigationDirection {
|
|
case previous
|
|
case next
|
|
}
|
|
|
|
protocol StatusItemMenuPersistentActionDelegate: AnyObject {
|
|
func performPersistentRefreshAction(
|
|
in menuID: ObjectIdentifier,
|
|
menuInteractionGeneration: Int)
|
|
func performPersistentSettingsAction()
|
|
func performPersistentQuitAction()
|
|
func performProviderNavigation(_ direction: StatusItemMenuProviderNavigationDirection)
|
|
}
|
|
|
|
final class StatusItemMenu: NSMenu {
|
|
weak var persistentActionDelegate: StatusItemMenuPersistentActionDelegate?
|
|
var menuInteractionGeneration: Int?
|
|
|
|
func requestPersistentRefreshAction() {
|
|
guard let menuInteractionGeneration else { return }
|
|
self.persistentActionDelegate?.performPersistentRefreshAction(
|
|
in: ObjectIdentifier(self),
|
|
menuInteractionGeneration: menuInteractionGeneration)
|
|
}
|
|
|
|
override func performKeyEquivalent(with event: NSEvent) -> Bool {
|
|
if let action = Self.persistentAction(for: event) {
|
|
switch action {
|
|
case .refresh:
|
|
self.requestPersistentRefreshAction()
|
|
case .settings:
|
|
self.persistentActionDelegate?.performPersistentSettingsAction()
|
|
case .quit:
|
|
self.persistentActionDelegate?.performPersistentQuitAction()
|
|
}
|
|
return true
|
|
}
|
|
if let direction = Self.providerNavigationDirection(for: event),
|
|
self.items.first?.view is ProviderSwitcherView
|
|
{
|
|
self.persistentActionDelegate?.performProviderNavigation(direction)
|
|
return true
|
|
}
|
|
|
|
return super.performKeyEquivalent(with: event)
|
|
}
|
|
|
|
private enum PersistentAction {
|
|
case refresh
|
|
case settings
|
|
case quit
|
|
}
|
|
|
|
nonisolated static func isPersistentRefreshShortcut(for event: NSEvent) -> Bool {
|
|
self.persistentAction(for: event) == .refresh
|
|
}
|
|
|
|
private nonisolated static func persistentAction(for event: NSEvent) -> PersistentAction? {
|
|
guard event.type == .keyDown else { return nil }
|
|
|
|
let relevantModifiers = event.modifierFlags.intersection([.command, .option, .control, .shift])
|
|
guard relevantModifiers == .command else { return nil }
|
|
|
|
switch event.charactersIgnoringModifiers?.lowercased() {
|
|
case "r":
|
|
return .refresh
|
|
case ",":
|
|
return .settings
|
|
case "q":
|
|
return .quit
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
nonisolated static func providerNavigationDirection(
|
|
for event: NSEvent) -> StatusItemMenuProviderNavigationDirection?
|
|
{
|
|
guard event.type == .keyDown else { return nil }
|
|
let relevantModifiers = event.modifierFlags.intersection([.command, .option, .control, .shift])
|
|
guard relevantModifiers.isEmpty else { return nil }
|
|
switch event.keyCode {
|
|
case 123:
|
|
return .previous
|
|
case 124:
|
|
return .next
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
nonisolated static func providerSelectionIndex(for event: NSEvent) -> Int? {
|
|
guard event.type == .keyDown else { return nil }
|
|
let relevantModifiers = event.modifierFlags.intersection([.command, .option, .control, .shift])
|
|
guard relevantModifiers == .command,
|
|
let characters = event.charactersIgnoringModifiers,
|
|
characters.count == 1,
|
|
let number = Int(characters),
|
|
(1...9).contains(number)
|
|
else {
|
|
return nil
|
|
}
|
|
return number - 1
|
|
}
|
|
}
|