fix: 修复悬浮窗顶部异常矩形并完善背板行为
This commit is contained in:
parent
6bc4971051
commit
38a1956c7f
@ -6,6 +6,7 @@ extension Notification.Name {
|
||||
static let showSettingsPanel = Notification.Name("FloatingMonitor.ShowSettings")
|
||||
static let resizeMonitorWindow = Notification.Name("FloatingMonitor.ResizeWindow")
|
||||
static let refreshIntervalChanged = Notification.Name("FloatingMonitor.RefreshIntervalChanged")
|
||||
static let backdropEnabledChanged = Notification.Name("FloatingMonitor.BackdropEnabledChanged")
|
||||
}
|
||||
|
||||
// MARK: - AppDelegate
|
||||
|
||||
@ -21,7 +21,11 @@ final class FloatingWindow: NSWindow {
|
||||
self.hasShadow = true
|
||||
self.isMovableByWindowBackground = true
|
||||
self.minSize = NSSize(width: 240, height: 200)
|
||||
self.titlebarAppearsTransparent = true
|
||||
self.titleVisibility = .hidden
|
||||
|
||||
// 替换默认 contentView 为透明视图,防止系统默认灰色背景露出
|
||||
let view = NSView()
|
||||
view.wantsLayer = true
|
||||
view.layer?.backgroundColor = NSColor.clear.cgColor
|
||||
self.contentView = view
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,14 +6,86 @@ final class FloatingWindowController: NSWindowController {
|
||||
private var hostingView: NSHostingView<ContentView>?
|
||||
private var visualEffect: NSVisualEffectView!
|
||||
|
||||
private let debugLogPath = NSHomeDirectory() + "/Desktop/fm_debug.log"
|
||||
|
||||
convenience init() {
|
||||
let window = FloatingWindow()
|
||||
let window = FloatingWindow(
|
||||
contentRect: NSRect(x: 0, y: 0, width: 250, height: 300),
|
||||
styleMask: [.borderless, .resizable],
|
||||
backing: .buffered,
|
||||
defer: false
|
||||
)
|
||||
window.isReleasedWhenClosed = false
|
||||
self.init(window: window)
|
||||
setupVisualEffect()
|
||||
observeBackdropToggle()
|
||||
clearDebugLog()
|
||||
}
|
||||
|
||||
// MARK: - 毛玻璃(只做合成锚点,alpha 始终=1)
|
||||
// MARK: - 调试日志
|
||||
|
||||
private func clearDebugLog() {
|
||||
try? "".write(toFile: debugLogPath, atomically: true, encoding: .utf8)
|
||||
}
|
||||
|
||||
private func log(_ msg: String) {
|
||||
let ts = ISO8601DateFormatter().string(from: Date())
|
||||
let line = "[\(ts)] \(msg)\n"
|
||||
if let data = line.data(using: .utf8) {
|
||||
if let handle = FileHandle(forWritingAtPath: debugLogPath) {
|
||||
handle.seekToEndOfFile()
|
||||
handle.write(data)
|
||||
handle.closeFile()
|
||||
} else {
|
||||
try? data.write(to: URL(fileURLWithPath: debugLogPath))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func dumpGeometry(_ tag: String) {
|
||||
guard let window = window else { return }
|
||||
log("===== \(tag) =====")
|
||||
log("window.frame = \(window.frame)")
|
||||
log("window.styleMask = \(window.styleMask.rawValue)")
|
||||
log("window.isOpaque = \(window.isOpaque)")
|
||||
log("window.hasShadow = \(window.hasShadow)")
|
||||
log("window.level = \(window.level.rawValue)")
|
||||
log("window.backgroundColor= \(String(describing: window.backgroundColor))")
|
||||
log("window.isVisible = \(window.isVisible)")
|
||||
log("window.titlebarAppearsTransparent = \(window.titlebarAppearsTransparent)")
|
||||
|
||||
guard let cv = window.contentView else { return }
|
||||
log("--- contentView ---")
|
||||
log("contentView.frame = \(cv.frame)")
|
||||
log("contentView.bounds = \(cv.bounds)")
|
||||
log("contentView.subviews.count = \(cv.subviews.count)")
|
||||
|
||||
for (i, sv) in cv.subviews.enumerated() {
|
||||
log(" subview[\(i)]: \(type(of: sv))")
|
||||
log(" frame = \(sv.frame)")
|
||||
log(" bounds = \(sv.bounds)")
|
||||
log(" isHidden = \(sv.isHidden)")
|
||||
log(" alphaValue = \(sv.alphaValue)")
|
||||
log(" wantsLayer = \(sv.wantsLayer)")
|
||||
if let layer = sv.layer {
|
||||
log(" layer.frame = \(layer.frame)")
|
||||
log(" layer.bounds = \(layer.bounds)")
|
||||
log(" layer.cornerRadius= \(layer.cornerRadius)")
|
||||
log(" layer.masksToBounds=\(layer.masksToBounds)")
|
||||
log(" layer.isHidden = \(layer.isHidden)")
|
||||
log(" layer.opacity = \(layer.opacity)")
|
||||
log(" layer.sublayers?.count = \(layer.sublayers?.count ?? 0)")
|
||||
if let subs = layer.sublayers {
|
||||
for (j, sl) in subs.enumerated() {
|
||||
log(" sublayer[\(j)]: \(type(of: sl)) frame=\(sl.frame) bounds=\(sl.bounds) hidden=\(sl.isHidden)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
log("")
|
||||
}
|
||||
|
||||
// MARK: - 毛玻璃背板
|
||||
|
||||
private func setupVisualEffect() {
|
||||
guard let window = window else { return }
|
||||
@ -23,7 +95,7 @@ final class FloatingWindowController: NSWindowController {
|
||||
ve.material = .hudWindow
|
||||
ve.blendingMode = .behindWindow
|
||||
ve.state = .active
|
||||
ve.alphaValue = 1.0 // 始终不透明,维持窗口合成通道
|
||||
ve.alphaValue = 1.0
|
||||
ve.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
ve.layer?.cornerRadius = 16
|
||||
@ -43,6 +115,35 @@ final class FloatingWindowController: NSWindowController {
|
||||
ve.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||
ve.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
||||
])
|
||||
|
||||
applyBackdropEnabled(Configuration.shared.backdropEnabled)
|
||||
}
|
||||
|
||||
// MARK: - 背板开关
|
||||
|
||||
private func observeBackdropToggle() {
|
||||
NotificationCenter.default.addObserver(
|
||||
forName: .backdropEnabledChanged, object: nil, queue: .main
|
||||
) { [weak self] note in
|
||||
guard let enabled = note.object as? Bool else { return }
|
||||
self?.applyBackdropEnabled(enabled)
|
||||
}
|
||||
}
|
||||
|
||||
private func applyBackdropEnabled(_ enabled: Bool) {
|
||||
guard let ve = visualEffect else { return }
|
||||
if enabled {
|
||||
ve.blendingMode = .behindWindow
|
||||
ve.alphaValue = 1.0
|
||||
ve.layer?.backgroundColor = NSColor.black.withAlphaComponent(0.35).cgColor
|
||||
} else {
|
||||
ve.blendingMode = .withinWindow
|
||||
ve.alphaValue = 0.0
|
||||
ve.layer?.backgroundColor = NSColor.clear.cgColor
|
||||
}
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
|
||||
self?.dumpGeometry("backdropEnabled=\(enabled)")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SwiftUI 嵌入
|
||||
@ -52,7 +153,7 @@ final class FloatingWindowController: NSWindowController {
|
||||
|
||||
let hv = NSHostingView(rootView: rootView)
|
||||
hv.wantsLayer = true
|
||||
hv.layer?.cornerRadius = 16
|
||||
hv.layer?.cornerRadius = Configuration.shared.backdropEnabled ? 16 : 0
|
||||
hv.layer?.masksToBounds = true
|
||||
hv.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
@ -88,6 +189,9 @@ final class FloatingWindowController: NSWindowController {
|
||||
}
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
NSApp.activate(ignoringOtherApps: false)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
|
||||
self?.dumpGeometry("show")
|
||||
}
|
||||
}
|
||||
|
||||
func hide() {
|
||||
|
||||
@ -110,6 +110,16 @@ final class Configuration: ObservableObject {
|
||||
didSet { save("windowHeight", windowHeight) }
|
||||
}
|
||||
|
||||
@Published var backdropEnabled: Bool {
|
||||
didSet {
|
||||
save("backdropEnabled", backdropEnabled)
|
||||
NotificationCenter.default.post(name: .backdropEnabledChanged, object: backdropEnabled)
|
||||
}
|
||||
}
|
||||
@Published var invertTextColor: Bool {
|
||||
didSet { save("invertTextColor", invertTextColor) }
|
||||
}
|
||||
|
||||
@Published var gaugeAnimationEnabled: Bool {
|
||||
didSet { save("gaugeAnimationEnabled", gaugeAnimationEnabled) }
|
||||
}
|
||||
@ -192,6 +202,8 @@ final class Configuration: ObservableObject {
|
||||
fontSize = defaults.object(forKey: "fontSize") as? Double ?? 12
|
||||
windowWidth = defaults.object(forKey: "windowWidth") as? Double ?? 250
|
||||
windowHeight = defaults.object(forKey: "windowHeight") as? Double ?? 300
|
||||
backdropEnabled = defaults.object(forKey: "backdropEnabled") as? Bool ?? true
|
||||
invertTextColor = defaults.object(forKey: "invertTextColor") as? Bool ?? false
|
||||
|
||||
gaugeAnimationEnabled = defaults.object(forKey: "gaugeAnimationEnabled") as? Bool ?? true
|
||||
textAnimationEnabled = defaults.object(forKey: "textAnimationEnabled") as? Bool ?? true
|
||||
|
||||
@ -33,6 +33,7 @@ struct ContentView: View {
|
||||
}
|
||||
.frame(minWidth: 240, idealWidth: 280, maxWidth: 400,
|
||||
minHeight: 200, idealHeight: 480, maxHeight: 700)
|
||||
.environment(\.colorScheme, config.invertTextColor ? .dark : .light)
|
||||
.onAppear {
|
||||
monitor.interval = config.refreshInterval
|
||||
monitor.topN = config.processCount
|
||||
|
||||
@ -269,6 +269,26 @@ struct SettingsView: View {
|
||||
.frame(width: 38, alignment: .trailing)
|
||||
}
|
||||
|
||||
HStack {
|
||||
Text("毛玻璃背板")
|
||||
.font(.system(size: 12))
|
||||
Spacer()
|
||||
Toggle("", isOn: $config.backdropEnabled)
|
||||
.toggleStyle(.switch)
|
||||
.scaleEffect(0.7)
|
||||
.frame(width: 38)
|
||||
}
|
||||
|
||||
HStack {
|
||||
Text("反转文字颜色")
|
||||
.font(.system(size: 12))
|
||||
Spacer()
|
||||
Toggle("", isOn: $config.invertTextColor)
|
||||
.toggleStyle(.switch)
|
||||
.scaleEffect(0.7)
|
||||
.frame(width: 38)
|
||||
}
|
||||
|
||||
HStack {
|
||||
Text("图形动画")
|
||||
.font(.system(size: 12))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user