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 showSettingsPanel = Notification.Name("FloatingMonitor.ShowSettings")
|
||||||
static let resizeMonitorWindow = Notification.Name("FloatingMonitor.ResizeWindow")
|
static let resizeMonitorWindow = Notification.Name("FloatingMonitor.ResizeWindow")
|
||||||
static let refreshIntervalChanged = Notification.Name("FloatingMonitor.RefreshIntervalChanged")
|
static let refreshIntervalChanged = Notification.Name("FloatingMonitor.RefreshIntervalChanged")
|
||||||
|
static let backdropEnabledChanged = Notification.Name("FloatingMonitor.BackdropEnabledChanged")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - AppDelegate
|
// MARK: - AppDelegate
|
||||||
|
|||||||
@ -21,7 +21,11 @@ final class FloatingWindow: NSWindow {
|
|||||||
self.hasShadow = true
|
self.hasShadow = true
|
||||||
self.isMovableByWindowBackground = true
|
self.isMovableByWindowBackground = true
|
||||||
self.minSize = NSSize(width: 240, height: 200)
|
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 hostingView: NSHostingView<ContentView>?
|
||||||
private var visualEffect: NSVisualEffectView!
|
private var visualEffect: NSVisualEffectView!
|
||||||
|
|
||||||
|
private let debugLogPath = NSHomeDirectory() + "/Desktop/fm_debug.log"
|
||||||
|
|
||||||
convenience init() {
|
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
|
window.isReleasedWhenClosed = false
|
||||||
self.init(window: window)
|
self.init(window: window)
|
||||||
setupVisualEffect()
|
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() {
|
private func setupVisualEffect() {
|
||||||
guard let window = window else { return }
|
guard let window = window else { return }
|
||||||
@ -23,7 +95,7 @@ final class FloatingWindowController: NSWindowController {
|
|||||||
ve.material = .hudWindow
|
ve.material = .hudWindow
|
||||||
ve.blendingMode = .behindWindow
|
ve.blendingMode = .behindWindow
|
||||||
ve.state = .active
|
ve.state = .active
|
||||||
ve.alphaValue = 1.0 // 始终不透明,维持窗口合成通道
|
ve.alphaValue = 1.0
|
||||||
ve.translatesAutoresizingMaskIntoConstraints = false
|
ve.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
||||||
ve.layer?.cornerRadius = 16
|
ve.layer?.cornerRadius = 16
|
||||||
@ -43,6 +115,35 @@ final class FloatingWindowController: NSWindowController {
|
|||||||
ve.topAnchor.constraint(equalTo: contentView.topAnchor),
|
ve.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||||
ve.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
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 嵌入
|
// MARK: - SwiftUI 嵌入
|
||||||
@ -52,7 +153,7 @@ final class FloatingWindowController: NSWindowController {
|
|||||||
|
|
||||||
let hv = NSHostingView(rootView: rootView)
|
let hv = NSHostingView(rootView: rootView)
|
||||||
hv.wantsLayer = true
|
hv.wantsLayer = true
|
||||||
hv.layer?.cornerRadius = 16
|
hv.layer?.cornerRadius = Configuration.shared.backdropEnabled ? 16 : 0
|
||||||
hv.layer?.masksToBounds = true
|
hv.layer?.masksToBounds = true
|
||||||
hv.translatesAutoresizingMaskIntoConstraints = false
|
hv.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
||||||
@ -88,6 +189,9 @@ final class FloatingWindowController: NSWindowController {
|
|||||||
}
|
}
|
||||||
window.makeKeyAndOrderFront(nil)
|
window.makeKeyAndOrderFront(nil)
|
||||||
NSApp.activate(ignoringOtherApps: false)
|
NSApp.activate(ignoringOtherApps: false)
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
|
||||||
|
self?.dumpGeometry("show")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func hide() {
|
func hide() {
|
||||||
|
|||||||
@ -110,6 +110,16 @@ final class Configuration: ObservableObject {
|
|||||||
didSet { save("windowHeight", windowHeight) }
|
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 {
|
@Published var gaugeAnimationEnabled: Bool {
|
||||||
didSet { save("gaugeAnimationEnabled", gaugeAnimationEnabled) }
|
didSet { save("gaugeAnimationEnabled", gaugeAnimationEnabled) }
|
||||||
}
|
}
|
||||||
@ -192,6 +202,8 @@ final class Configuration: ObservableObject {
|
|||||||
fontSize = defaults.object(forKey: "fontSize") as? Double ?? 12
|
fontSize = defaults.object(forKey: "fontSize") as? Double ?? 12
|
||||||
windowWidth = defaults.object(forKey: "windowWidth") as? Double ?? 250
|
windowWidth = defaults.object(forKey: "windowWidth") as? Double ?? 250
|
||||||
windowHeight = defaults.object(forKey: "windowHeight") as? Double ?? 300
|
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
|
gaugeAnimationEnabled = defaults.object(forKey: "gaugeAnimationEnabled") as? Bool ?? true
|
||||||
textAnimationEnabled = defaults.object(forKey: "textAnimationEnabled") 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,
|
.frame(minWidth: 240, idealWidth: 280, maxWidth: 400,
|
||||||
minHeight: 200, idealHeight: 480, maxHeight: 700)
|
minHeight: 200, idealHeight: 480, maxHeight: 700)
|
||||||
|
.environment(\.colorScheme, config.invertTextColor ? .dark : .light)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
monitor.interval = config.refreshInterval
|
monitor.interval = config.refreshInterval
|
||||||
monitor.topN = config.processCount
|
monitor.topN = config.processCount
|
||||||
|
|||||||
@ -269,6 +269,26 @@ struct SettingsView: View {
|
|||||||
.frame(width: 38, alignment: .trailing)
|
.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 {
|
HStack {
|
||||||
Text("图形动画")
|
Text("图形动画")
|
||||||
.font(.system(size: 12))
|
.font(.system(size: 12))
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user