mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 12:03:59 +00:00
- Moved prole-tools-app to prole-app at the project root to make it self-contained for transition to its own repository. - Created prole-tools-app/dist/ directory to host build artifacts. - Generated distribution artifacts (Prole Tools.app and Prole Tools.zip) using prole-app/build.sh package. - Checked in the generated artifacts to prole-tools-app/dist/ (bypassing .gitignore for temporary release process). Changes Summary • Renamed directory prole-tools-app/ to prole-app/. • Populated prole-tools-app/dist/ with the latest build output from prole-app/build.sh. • Staged all changes, including the forced addition of ignored artifacts in prole-tools-app/dist/.
134 lines
4.7 KiB
Swift
134 lines
4.7 KiB
Swift
import AppKit
|
|
|
|
final class StatusItemController {
|
|
struct Actions {
|
|
let onLeftClick: () -> Void
|
|
let onToggleStatusBar: () -> Void
|
|
let onToggleAppWindow: () -> Void
|
|
let onRefresh: () -> Void
|
|
let onQuit: () -> Void
|
|
}
|
|
|
|
private var statusItem: NSStatusItem?
|
|
private let actions: Actions
|
|
private var stateStatusBarVisible: Bool = true
|
|
private var stateAppWindowVisible: Bool = false
|
|
|
|
init(actions: Actions) {
|
|
self.actions = actions
|
|
createStatusItemIfNeeded()
|
|
}
|
|
|
|
func setState(statusBarVisible: Bool, appWindowVisible: Bool) {
|
|
self.stateStatusBarVisible = statusBarVisible
|
|
self.stateAppWindowVisible = appWindowVisible
|
|
}
|
|
|
|
func hideStatusItem() {
|
|
if let item = statusItem {
|
|
NSStatusBar.system.removeStatusItem(item)
|
|
statusItem = nil
|
|
}
|
|
}
|
|
|
|
func showStatusItem() {
|
|
createStatusItemIfNeeded()
|
|
}
|
|
|
|
var isVisible: Bool { statusItem != nil }
|
|
|
|
private func createStatusItemIfNeeded() {
|
|
guard statusItem == nil else { return }
|
|
let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
|
statusItem = item
|
|
if let button = item.button {
|
|
// Use a proper template image so it always tints/appears in light/dark menu bar
|
|
button.image = StatusItemController.makeTemplateGlyphImage("P")
|
|
button.imagePosition = .imageOnly
|
|
button.target = self
|
|
button.action = #selector(handleClick)
|
|
// Only respond to left click; right click intentionally does nothing
|
|
button.sendAction(on: [.leftMouseUp])
|
|
button.toolTip = "Prole Tools — Click for menu"
|
|
button.appearsDisabled = false
|
|
}
|
|
}
|
|
|
|
@objc private func handleClick(_ sender: Any?) {
|
|
// Always open the context menu on left click
|
|
showMenu()
|
|
}
|
|
|
|
private func showMenu() {
|
|
guard let button = statusItem?.button else { return }
|
|
let menu = NSMenu(title: "ProleStatus")
|
|
|
|
let statusBarTitle = stateStatusBarVisible ? "Hide Status Bar Icon" : "Show Status Bar Icon"
|
|
let appWindowTitle = stateAppWindowVisible ? "Hide Main Window" : "Show Main Window"
|
|
|
|
let sbItem = NSMenuItem(title: statusBarTitle, action: #selector(toggleStatusBar), keyEquivalent: "")
|
|
sbItem.target = self
|
|
menu.addItem(sbItem)
|
|
|
|
let winItem = NSMenuItem(title: appWindowTitle, action: #selector(toggleAppWindow), keyEquivalent: "")
|
|
winItem.target = self
|
|
menu.addItem(winItem)
|
|
|
|
menu.addItem(.separator())
|
|
|
|
let refreshItem = NSMenuItem(title: "Refresh Now", action: #selector(refreshNow), keyEquivalent: "r")
|
|
refreshItem.target = self
|
|
menu.addItem(refreshItem)
|
|
|
|
menu.addItem(.separator())
|
|
|
|
let quitItem = NSMenuItem(title: "Quit Prole Tools", action: #selector(quitApp), keyEquivalent: "q")
|
|
quitItem.target = self
|
|
menu.addItem(quitItem)
|
|
|
|
let point = NSPoint(x: 0, y: button.bounds.minY - 3)
|
|
menu.popUp(positioning: nil, at: point, in: button)
|
|
}
|
|
|
|
@objc private func toggleStatusBar() { actions.onToggleStatusBar() }
|
|
@objc private func toggleAppWindow() { actions.onToggleAppWindow() }
|
|
@objc private func refreshNow() { actions.onRefresh() }
|
|
@objc private func quitApp() { actions.onQuit() }
|
|
}
|
|
|
|
// MARK: - Icon drawing
|
|
extension StatusItemController {
|
|
/// Create a monochrome template image from a single-character glyph to use as a status bar icon.
|
|
/// The image is marked as template so macOS tints it appropriately.
|
|
fileprivate static func makeTemplateGlyphImage(_ char: Character) -> NSImage? {
|
|
let size = NSSize(width: 18, height: 18)
|
|
let image = NSImage(size: size)
|
|
image.lockFocus()
|
|
defer { image.unlockFocus() }
|
|
|
|
// Clear background (transparent)
|
|
NSColor.clear.set()
|
|
NSBezierPath(rect: NSRect(origin: .zero, size: size)).fill()
|
|
|
|
// Draw the glyph centered
|
|
let string = String(char)
|
|
let font = NSFont.monospacedSystemFont(ofSize: 14, weight: .bold)
|
|
let attrs: [NSAttributedString.Key: Any] = [
|
|
.font: font,
|
|
.foregroundColor: NSColor.black // template ignores color; use black base
|
|
]
|
|
let attributed = NSAttributedString(string: string, attributes: attrs)
|
|
let textSize = attributed.size()
|
|
let rect = NSRect(
|
|
x: (size.width - textSize.width) / 2.0,
|
|
y: (size.height - textSize.height) / 2.0,
|
|
width: textSize.width,
|
|
height: textSize.height
|
|
)
|
|
attributed.draw(in: rect)
|
|
|
|
image.isTemplate = true
|
|
return image
|
|
}
|
|
}
|