Initial commit.

This commit is contained in:
2024-12-27 15:30:07 -08:00
commit 4795f95416
58 changed files with 9449 additions and 0 deletions

217
src/CBMenuBarItem.swift Normal file
View File

@@ -0,0 +1,217 @@
//
// MIT License
//
// Copyright (c) 2022 Lukas Romsicki
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// Source Code: https://github.com/lfroms/fluid-menu-bar-extra
// Some parts of the code were altered to fit the needs of this application.
// Any modifications that were made are still licensed under the author's
// original license (aka MIT license). Contents of this file are excluded
// from the coverage of the license being applied to the rest of the code.
//
import Cocoa
import os
final class CBMenuBarItem: NSObject, NSWindowDelegate {
private var statusItem: NSStatusItem
var panel: NSPanel
private var localEventMonitor: EventMonitor?
private var localDragEventMonitor: EventMonitor?
private var globalEventMonitor: EventMonitor?
override init() {
self.statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
self.statusItem.isVisible = true
self.panel = PopoverPanel(viewController: CmdViewController())
super.init()
// Events
// Shows panel and keeps the button highlighted.
localEventMonitor = LocalEventMonitor(mask: [.leftMouseDown]) { [weak self] event in
if let button = self?.statusItem.button, event.window == button.window, !event.modifierFlags.contains(.command) {
self?.statusButtonPressed(button)
return nil
}
return event
}
// On click and drag, the button should panel should desappear and
// un-highlight.
localDragEventMonitor = LocalEventMonitor(mask: [.leftMouseDragged, .keyDown]) { [weak self] event in
if let panel = self?.panel, panel.isKeyWindow {
if event.modifierFlags.contains(.command) && event.type == .leftMouseDragged {
self?.panel.resignKey()
}
}
return event
}
// NOTE: The need for this seems a bit questionable. Maybe, this
// works properly without global event monitor in MacOS Sequoia?
// Resign key whenever clicking outside of panel, thus hiding the
// panel and un-highlighting the button.
// In order to receive events, the program needs to be codesigned.
globalEventMonitor = GlobalEventMonitor(mask: [.leftMouseDown, .rightMouseDown]) { [weak self] event in
if let panel = self?.panel, panel.isKeyWindow {
panel.resignKey()
}
}
if let statusButton = statusItem.button,
let statusButtonWindow = statusButton.window {
statusButtonWindow.delegate = self
}
self.panel.delegate = self
localEventMonitor?.start()
localDragEventMonitor?.start()
}
deinit {
persistMenuBar(false)
localEventMonitor?.stop()
localDragEventMonitor?.stop()
globalEventMonitor?.stop()
panel.close()
NSStatusBar.system.removeStatusItem(statusItem)
}
@objc private func statusButtonPressed(_ sender: NSButton) {
if panel.isVisible {
panel.resignKey()
return
}
showPanel()
}
private func showPanel() {
setButtonHighlighted(to: true)
persistMenuBar(true)
setPanelPosition()
panel.makeKeyAndOrderFront(nil)
}
private func dismissPanel() {
NSAnimationContext.runAnimationGroup { context in
context.duration = 0.3
context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
panel.animator().alphaValue = 0
} completionHandler: { [weak self] in
self?.panel.orderOut(nil)
self?.panel.alphaValue = 1
self?.setButtonHighlighted(to: false)
persistMenuBar(false)
}
}
private func setPanelPosition() {
guard let statusItemView = statusItem.button?.window else {
panel.center()
return
}
var targetRect = statusItemView.frame
if let screen = statusItemView.screen {
let panelWidth = panel.frame.width
if statusItemView.frame.origin.x + panelWidth > screen.visibleFrame.width {
targetRect.origin.x += statusItemView.frame.width
targetRect.origin.x -= panelWidth
targetRect.origin.x += Metrics.windowBorderSize
} else {
targetRect.origin.x -= Metrics.windowBorderSize
}
} else {
targetRect.origin.x -= Metrics.windowBorderSize
}
if targetRect.origin.x < 0 {
targetRect.origin.x = 0
}
panel.setFrameTopLeftPoint(targetRect.origin)
}
private func setButtonHighlighted(to highlight: Bool) {
self.statusItem.button!.highlight(highlight)
}
func setImage(title: String?, description: String) {
if title != nil {
statusItem.button!.image = NSImage(systemSymbolName: title!, accessibilityDescription: description)
return
}
statusItem.button!.image = nil
}
func setTitle(_ title: String) {
statusItem.button!.title = title
}
func setAutosaveName(_ name: String) {
statusItem.autosaveName = name
}
func setContents(to text: String) {
guard let viewContoller = panel.contentViewController as? CmdViewController else { return }
viewContoller.setText(text)
}
func setFile(_ file: CmdFile) {
guard let viewContoller = panel.contentViewController as? CmdViewController else { return }
viewContoller.setFile(file)
}
func windowDidResize(_ notification: Notification) {
if let cmdPanel = notification.object as? NSPanel, panel == cmdPanel {
setPanelPosition()
}
}
func windowDidMove(_ notification: Notification) {
if let statusBarButtonWindow = notification.object as? NSWindow,
let buttonWindow = statusItem.button?.window,
buttonWindow == statusBarButtonWindow
{
setPanelPosition()
}
}
func windowDidBecomeKey(_ notification: Notification) {
globalEventMonitor?.start()
}
func windowDidResignKey(_ notification: Notification) {
globalEventMonitor?.stop()
dismissPanel()
}
}
// MARK: - Metrics
fileprivate enum Metrics {
static let windowBorderSize: CGFloat = 2
}