MenuController.swift

2.6 KB · raw

1import AppKit2import SwiftUI34@MainActor5class MenuController: NSObject {6    static private(set) weak var shared: MenuController?78    let statusItem: NSStatusItem9    let popover = NSPopover()10    var eventMonitor: EventMonitor?1112    init(statusItem: NSStatusItem) {13        self.statusItem = statusItem14        super.init()1516        if let button = statusItem.button {17            button.action = #selector(togglePopover(_:))18            button.target = self19        }2021        // Explicitly set content size to match MainView's frame22        popover.contentSize = NSSize(width: 450, height: 640)23        popover.behavior = .transient24        popover.contentViewController = NSHostingController(rootView: MainView())2526        eventMonitor = EventMonitor(mask: [.leftMouseDown, .rightMouseDown]) { [weak self] event in27            if let self = self, self.popover.isShown {28                self.closePopover(sender: event)29            }30        }3132        MenuController.shared = self33    }3435    /// Keep the popover open while a modal window (e.g. an NSOpenPanel) is up.36    /// A transient popover otherwise dismisses itself the moment another window37    /// takes focus, which closed the folder picker before it could be used.38    func beginModalPresentation() {39        eventMonitor?.stop()40        popover.behavior = .applicationDefined41    }4243    func endModalPresentation() {44        popover.behavior = .transient45        if popover.isShown {46            eventMonitor?.start()47        }48    }49    50    @objc func togglePopover(_ sender: AnyObject?) {51        if popover.isShown {52            closePopover(sender: sender)53        } else {54            showPopover(sender: sender)55        }56    }57    58    func showPopover(sender: AnyObject?) {59        if let button = statusItem.button {60            popover.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)61            eventMonitor?.start()62        }63    }64    65    func closePopover(sender: AnyObject?) {66        popover.performClose(sender)67        eventMonitor?.stop()68    }69}7071class EventMonitor {72    private var monitor: Any?73    private let mask: NSEvent.EventTypeMask74    private let handler: (NSEvent?) -> Void75    76    init(mask: NSEvent.EventTypeMask, handler: @escaping (NSEvent?) -> Void) {77        self.mask = mask78        self.handler = handler79    }80    81    deinit {82        stop()83    }84    85    func start() {86        monitor = NSEvent.addGlobalMonitorForEvents(matching: mask, handler: handler)87    }88    89    func stop() {90        if let monitor = monitor {91            NSEvent.removeMonitor(monitor)92            self.monitor = nil93        }94    }95}