prole/proleStatus/Sources/OverlayWindow.swift
2025-11-17 20:52:19 -08:00

107 lines
4.3 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import AppKit
final class OverlayWindowController: NSWindowController {
private let statusView = StatusView()
private let visualEffect = NSVisualEffectView()
init(serviceChecker: ServiceChecker) {
let style: NSWindow.StyleMask = [.borderless]
let window = OverlayWindow(contentRect: .zero, styleMask: style, backing: .buffered, defer: false)
super.init(window: window)
window.isReleasedWhenClosed = false
window.level = .floating // below status bar level, above normal windows
window.collectionBehavior = [.canJoinAllSpaces, .stationary]
window.hasShadow = false
window.backgroundColor = .clear
window.isOpaque = false
// Visual effect that matches the menu bar
visualEffect.material = .menu
visualEffect.blendingMode = .behindWindow
visualEffect.state = .active
visualEffect.translatesAutoresizingMaskIntoConstraints = false
statusView.translatesAutoresizingMaskIntoConstraints = false
let container = NSView()
container.wantsLayer = true
container.translatesAutoresizingMaskIntoConstraints = false
container.addSubview(visualEffect)
visualEffect.addSubview(statusView)
window.contentView = container
NSLayoutConstraint.activate([
visualEffect.leadingAnchor.constraint(equalTo: container.leadingAnchor),
visualEffect.trailingAnchor.constraint(equalTo: container.trailingAnchor),
visualEffect.topAnchor.constraint(equalTo: container.topAnchor),
visualEffect.bottomAnchor.constraint(equalTo: container.bottomAnchor),
statusView.centerXAnchor.constraint(equalTo: visualEffect.centerXAnchor),
statusView.centerYAnchor.constraint(equalTo: visualEffect.centerYAnchor)
])
statusView.bindTo(serviceChecker: serviceChecker)
// Initial placement
recomputeFrameAndVisibility()
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
func showIfMenuBarVisible() { recomputeFrameAndVisibility() }
func recomputeFrameAndVisibility() {
guard let screen = NSScreen.main else { window?.orderOut(nil); return }
let frame = screen.frame
let visible = screen.visibleFrame
let menuBarHeight = frame.maxY - visible.maxY // 0 if auto-hidden or full-screen space
if menuBarHeight <= 0.5 { // treat as hidden/no menu bar
window?.orderOut(nil)
return
}
let barHeight = NSStatusBar.system.thickness // expected to be same as menu bar height
let y = visible.maxY - barHeight
let rect = NSRect(x: frame.minX, y: y, width: frame.width, height: barHeight)
window?.setFrame(rect, display: true)
window?.orderFrontRegardless()
}
func showContextMenu() {
guard let window = window, window.isVisible else { return }
let menu = NSMenu(title: "ProleStatus")
menu.addItem(withTitle: statusView.aggregateStatusSummary(), action: nil, keyEquivalent: "")
menu.addItem(.separator())
menu.addItem(withTitle: "Refresh Now", action: #selector(refreshNow), keyEquivalent: "r").target = self
menu.addItem(.separator())
menu.addItem(withTitle: "Quit ProleStatus", action: #selector(quit), keyEquivalent: "q").target = self
let point = NSPoint(x: window.frame.midX, y: window.frame.minY)
menu.popUp(positioning: nil, at: point, in: nil)
}
@objc private func refreshNow() {
NotificationCenter.default.post(name: ServiceChecker.forceRefreshNotification, object: nil)
}
@objc private func quit() {
NSApp.terminate(nil)
}
}
final class OverlayWindow: NSWindow {
override var canBecomeKey: Bool { false }
override var canBecomeMain: Bool { false }
override init(contentRect: NSRect, styleMask style: NSWindow.StyleMask, backing backingStoreType: NSWindow.BackingStoreType, defer flag: Bool) {
super.init(contentRect: contentRect, styleMask: style, backing: backingStoreType, defer: flag)
isOpaque = false
backgroundColor = .clear
ignoresMouseEvents = false // allow hover tracking, but we’ll pass clicks through via hitTest
acceptsMouseMovedEvents = true
}
}