fix: 修复悬浮窗顶部异常矩形并完善背板行为

This commit is contained in:
JOJO 2026-05-06 16:21:25 +08:00
parent 6bc4971051
commit 38a1956c7f
6 changed files with 148 additions and 6 deletions

View File

@ -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

View File

@ -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
}
}

View File

@ -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() {

View File

@ -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

View File

@ -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

View File

@ -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))