- 实时 CPU/GPU/内存/Swap 监控 + Top 进程 - 5 种计量器样式(圆环/条状/纯文本/扇形/紧凑) - 半透明悬浮窗 + 菜单栏图标控制 - 独立设置窗口,每模块可指定行号+列位布局 - 窗口宽高/透明度/字体全局可调
102 lines
3.5 KiB
Swift
102 lines
3.5 KiB
Swift
import SwiftUI
|
||
|
||
struct ProcessListView: View {
|
||
let processes: [SystemInfo.ProcessInfo]
|
||
let title: String
|
||
let icon: String
|
||
let isMemory: Bool // true = 显示内存,false = 显示 CPU%
|
||
let fontSize: Double
|
||
|
||
var body: some View {
|
||
VStack(alignment: .leading, spacing: 4) {
|
||
// 标题
|
||
HStack(spacing: 5) {
|
||
Image(systemName: icon)
|
||
.font(.system(size: fontSize))
|
||
.foregroundColor(.secondary)
|
||
Text(title)
|
||
.font(.system(size: fontSize, weight: .medium))
|
||
.foregroundColor(.secondary)
|
||
Spacer()
|
||
}
|
||
|
||
// 表头
|
||
HStack(spacing: 0) {
|
||
Text("进程")
|
||
.font(.system(size: fontSize - 2))
|
||
.foregroundColor(.secondary.opacity(0.6))
|
||
.frame(maxWidth: .infinity, alignment: .leading)
|
||
Text(isMemory ? "内存" : "CPU")
|
||
.font(.system(size: fontSize - 2))
|
||
.foregroundColor(.secondary.opacity(0.6))
|
||
.frame(width: 52, alignment: .trailing)
|
||
}
|
||
.padding(.horizontal, 4)
|
||
|
||
Divider()
|
||
.background(Color.white.opacity(0.1))
|
||
|
||
// 进程列表
|
||
ForEach(processes.prefix(5)) { proc in
|
||
processRow(proc)
|
||
}
|
||
}
|
||
}
|
||
|
||
private func processRow(_ proc: SystemInfo.ProcessInfo) -> some View {
|
||
HStack(spacing: 4) {
|
||
// 进程名
|
||
Text(proc.name.components(separatedBy: "/").last ?? proc.name)
|
||
.font(.system(size: fontSize - 1, design: .monospaced))
|
||
.foregroundColor(.primary.opacity(0.85))
|
||
.lineLimit(1)
|
||
.truncationMode(.tail)
|
||
|
||
Spacer()
|
||
|
||
// 数值
|
||
if isMemory {
|
||
Text(formatBytes(proc.memoryBytes))
|
||
.font(.system(size: fontSize - 1, weight: .medium, design: .monospaced))
|
||
.foregroundColor(memoryColor(proc.memoryBytes))
|
||
.frame(width: 52, alignment: .trailing)
|
||
} else {
|
||
Text(String(format: "%.1f%%", proc.cpuPercent))
|
||
.font(.system(size: fontSize - 1, weight: .medium, design: .monospaced))
|
||
.foregroundColor(cpuColor(proc.cpuPercent))
|
||
.frame(width: 52, alignment: .trailing)
|
||
}
|
||
}
|
||
.padding(.horizontal, 4)
|
||
.padding(.vertical, 2)
|
||
.contentShape(Rectangle())
|
||
}
|
||
|
||
private func formatBytes(_ bytes: UInt64) -> String {
|
||
if bytes >= 1_073_741_824 {
|
||
String(format: "%.1f GB", Double(bytes) / 1_073_741_824)
|
||
} else if bytes >= 1_048_576 {
|
||
String(format: "%.0f MB", Double(bytes) / 1_048_576)
|
||
} else if bytes >= 1_024 {
|
||
String(format: "%.0f KB", Double(bytes) / 1_024)
|
||
} else {
|
||
"\(bytes) B"
|
||
}
|
||
}
|
||
|
||
private func cpuColor(_ value: Double) -> Color {
|
||
if value < 20 { return .green.opacity(0.8) }
|
||
if value < 50 { return .yellow.opacity(0.8) }
|
||
if value < 80 { return .orange.opacity(0.8) }
|
||
return .red.opacity(0.8)
|
||
}
|
||
|
||
private func memoryColor(_ bytes: UInt64) -> Color {
|
||
let mb = Double(bytes) / 1_048_576
|
||
if mb < 200 { return .green.opacity(0.8) }
|
||
if mb < 500 { return .yellow.opacity(0.8) }
|
||
if mb < 1000 { return .orange.opacity(0.8) }
|
||
return .red.opacity(0.8)
|
||
}
|
||
}
|