- 实时 CPU/GPU/内存/Swap 监控 + Top 进程 - 5 种计量器样式(圆环/条状/纯文本/扇形/紧凑) - 半透明悬浮窗 + 菜单栏图标控制 - 独立设置窗口,每模块可指定行号+列位布局 - 窗口宽高/透明度/字体全局可调
117 lines
3.6 KiB
Swift
117 lines
3.6 KiB
Swift
import AppKit
|
||
import SwiftUI
|
||
|
||
/// 悬浮窗口控制器:管理毛玻璃效果 + SwiftUI 视图嵌入
|
||
final class FloatingWindowController: NSWindowController {
|
||
private var hostingView: NSHostingView<ContentView>?
|
||
private var visualEffect: NSVisualEffectView!
|
||
|
||
convenience init() {
|
||
let window = FloatingWindow()
|
||
window.isReleasedWhenClosed = false
|
||
self.init(window: window)
|
||
setupVisualEffect()
|
||
}
|
||
|
||
private func setupVisualEffect() {
|
||
guard let window = window else { return }
|
||
|
||
let ve = NSVisualEffectView()
|
||
ve.wantsLayer = true
|
||
ve.material = .hudWindow
|
||
ve.blendingMode = .behindWindow
|
||
ve.state = .active
|
||
ve.translatesAutoresizingMaskIntoConstraints = false
|
||
|
||
ve.layer?.cornerRadius = 16
|
||
ve.layer?.masksToBounds = true
|
||
ve.layer?.borderWidth = 0
|
||
ve.layer?.borderColor = NSColor.clear.cgColor
|
||
ve.layer?.backgroundColor = NSColor.black.withAlphaComponent(0.35).cgColor
|
||
|
||
self.visualEffect = ve
|
||
|
||
guard let contentView = window.contentView else { return }
|
||
contentView.addSubview(ve, positioned: .below, relativeTo: contentView.subviews.first)
|
||
|
||
NSLayoutConstraint.activate([
|
||
ve.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
||
ve.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
|
||
ve.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||
ve.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
||
])
|
||
}
|
||
|
||
func embed(rootView: ContentView) {
|
||
guard let window = window, let contentView = window.contentView else { return }
|
||
|
||
let hv = NSHostingView(rootView: rootView)
|
||
hv.wantsLayer = true
|
||
hv.layer?.cornerRadius = 16
|
||
hv.layer?.masksToBounds = true
|
||
hv.translatesAutoresizingMaskIntoConstraints = false
|
||
|
||
contentView.addSubview(hv)
|
||
|
||
NSLayoutConstraint.activate([
|
||
hv.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
||
hv.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
|
||
hv.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||
hv.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
||
])
|
||
|
||
self.hostingView = hv
|
||
}
|
||
|
||
// MARK: - 显隐
|
||
|
||
func toggleVisible() {
|
||
guard let window = window else { return }
|
||
if window.isVisible {
|
||
window.orderOut(nil)
|
||
} else {
|
||
positionNearMenuBar()
|
||
window.makeKeyAndOrderFront(nil)
|
||
NSApp.activate(ignoringOtherApps: false)
|
||
}
|
||
}
|
||
|
||
func show() {
|
||
guard let window = window else { return }
|
||
if !window.isVisible {
|
||
positionNearMenuBar()
|
||
}
|
||
window.makeKeyAndOrderFront(nil)
|
||
NSApp.activate(ignoringOtherApps: false)
|
||
}
|
||
|
||
func hide() {
|
||
window?.orderOut(nil)
|
||
}
|
||
|
||
var isVisible: Bool {
|
||
window?.isVisible ?? false
|
||
}
|
||
|
||
func applyOpacity(_ value: Double) {
|
||
window?.alphaValue = value
|
||
}
|
||
|
||
func resize(width: CGFloat, height: CGFloat) {
|
||
guard let window = window else { return }
|
||
var frame = window.frame
|
||
frame.size.width = width
|
||
frame.size.height = height
|
||
window.setFrame(frame, display: true, animate: true)
|
||
}
|
||
|
||
private func positionNearMenuBar() {
|
||
guard let window = window, let screen = NSScreen.main else { return }
|
||
let screenFrame = screen.visibleFrame
|
||
window.setFrameOrigin(NSPoint(
|
||
x: screenFrame.maxX - window.frame.width - 24,
|
||
y: screenFrame.maxY - window.frame.height - 24
|
||
))
|
||
}
|
||
}
|