325 lines
12 KiB
Swift
325 lines
12 KiB
Swift
import SwiftUI
|
||
|
||
struct SettingsView: View {
|
||
@ObservedObject var config = Configuration.shared
|
||
@ObservedObject var monitor: SystemMonitor
|
||
|
||
var body: some View {
|
||
VStack(spacing: 0) {
|
||
// 标题栏
|
||
HStack {
|
||
Text("显示设置")
|
||
.font(.system(size: 14, weight: .semibold))
|
||
Spacer()
|
||
Button(action: { closeWindow() }) {
|
||
Image(systemName: "xmark.circle.fill")
|
||
.font(.system(size: 16))
|
||
.foregroundColor(.secondary)
|
||
}
|
||
.buttonStyle(.plain)
|
||
}
|
||
.padding(.horizontal, 16)
|
||
.padding(.top, 16)
|
||
.padding(.bottom, 12)
|
||
|
||
Divider().background(Color.white.opacity(0.1))
|
||
|
||
ScrollView {
|
||
VStack(spacing: 16) {
|
||
// 显示/隐藏开关组
|
||
toggleGroup
|
||
|
||
Divider().background(Color.white.opacity(0.08))
|
||
|
||
// 各指标样式
|
||
styleGroup
|
||
|
||
Divider().background(Color.white.opacity(0.08))
|
||
|
||
// 进程设置
|
||
processGroup
|
||
|
||
Divider().background(Color.white.opacity(0.08))
|
||
|
||
// 其他设置
|
||
otherGroup
|
||
}
|
||
.padding(16)
|
||
}
|
||
|
||
// 底部关闭
|
||
HStack {
|
||
Spacer()
|
||
Button("完成") { closeWindow() }
|
||
.keyboardShortcut(.return)
|
||
}
|
||
.padding(.horizontal, 16)
|
||
.padding(.bottom, 12)
|
||
}
|
||
.frame(width: 340, height: 680)
|
||
.background(.ultraThinMaterial)
|
||
}
|
||
|
||
// MARK: - 显示/隐藏
|
||
|
||
private var toggleGroup: some View {
|
||
VStack(alignment: .leading, spacing: 8) {
|
||
Text("显示内容")
|
||
.font(.system(size: 11, weight: .semibold))
|
||
.foregroundColor(.secondary)
|
||
.padding(.leading, 4)
|
||
|
||
toggleRow("CPU", binding: $config.showCPU, icon: "cpu")
|
||
toggleRow("GPU", binding: $config.showGPU, icon: "display")
|
||
toggleRow("内存", binding: $config.showMemory, icon: "memorychip")
|
||
toggleRow("Swap", binding: $config.showSwap, icon: "arrow.left.arrow.right")
|
||
toggleRow("CPU 温度", binding: $config.showCPUTemperature, icon: "thermometer.medium")
|
||
toggleRow("GPU 温度", binding: $config.showGPUTemperature, icon: "thermometer.low")
|
||
toggleRow("进程列表", binding: $config.showTopProcesses, icon: "list.bullet")
|
||
}
|
||
}
|
||
|
||
private func toggleRow(_ label: String, binding: Binding<Bool>, icon: String) -> some View {
|
||
HStack {
|
||
Image(systemName: icon)
|
||
.frame(width: 18)
|
||
.foregroundColor(binding.wrappedValue ? .accentColor : .secondary.opacity(0.5))
|
||
Text(label)
|
||
.font(.system(size: 12))
|
||
Spacer()
|
||
Toggle("", isOn: binding)
|
||
.toggleStyle(.switch)
|
||
.scaleEffect(0.7)
|
||
.frame(width: 38)
|
||
}
|
||
}
|
||
|
||
// MARK: - 样式选择
|
||
|
||
private var styleGroup: some View {
|
||
VStack(alignment: .leading, spacing: 8) {
|
||
Text("样式 · 行号 · 列位")
|
||
.font(.system(size: 11, weight: .semibold))
|
||
.foregroundColor(.secondary)
|
||
.padding(.leading, 4)
|
||
|
||
styleRow("CPU", style: $config.cpuStyle, row: $config.cpuRow, pos: $config.cpuPos, icon: "cpu")
|
||
styleRow("GPU", style: $config.gpuStyle, row: $config.gpuRow, pos: $config.gpuPos, icon: "display")
|
||
styleRow("内存", style: $config.memoryStyle, row: $config.memoryRow, pos: $config.memoryPos, icon: "memorychip")
|
||
styleRow("Swap", style: $config.swapStyle, row: $config.swapRow, pos: $config.swapPos, icon: "arrow.left.arrow.right")
|
||
styleRow("CPU温度", style: $config.cpuTemperatureStyle, row: $config.cpuTemperatureRow, pos: $config.cpuTemperaturePos, icon: "thermometer.medium")
|
||
styleRow("GPU温度", style: $config.gpuTemperatureStyle, row: $config.gpuTemperatureRow, pos: $config.gpuTemperaturePos, icon: "thermometer.low")
|
||
styleRow("进程", style: .constant(.bar), row: $config.processesRow, pos: $config.processesPos,
|
||
icon: "list.bullet", styleDisabled: true)
|
||
}
|
||
}
|
||
|
||
private func styleRow(_ label: String, style: Binding<GaugeDisplayStyle>,
|
||
row: Binding<Int>, pos: Binding<ModulePosition>, icon: String,
|
||
styleDisabled: Bool = false) -> some View {
|
||
HStack(spacing: 6) {
|
||
Image(systemName: icon)
|
||
.frame(width: 14)
|
||
.foregroundColor(.secondary)
|
||
Text(label)
|
||
.font(.system(size: 11))
|
||
.frame(width: 26, alignment: .leading)
|
||
|
||
// 行号 stepper(+向上 -向下)
|
||
Stepper {
|
||
Text("")
|
||
} onIncrement: {
|
||
if row.wrappedValue > 1 { row.wrappedValue -= 1 }
|
||
} onDecrement: {
|
||
if row.wrappedValue < 10 { row.wrappedValue += 1 }
|
||
}
|
||
.labelsHidden()
|
||
.scaleEffect(0.7)
|
||
.frame(width: 24)
|
||
Text("\(row.wrappedValue)")
|
||
.font(.system(size: 11, design: .monospaced))
|
||
.foregroundColor(.secondary)
|
||
.frame(width: 14, alignment: .center)
|
||
|
||
Spacer()
|
||
|
||
if styleDisabled {
|
||
Text("—")
|
||
.foregroundColor(.secondary.opacity(0.4))
|
||
.frame(width: 64)
|
||
} else {
|
||
Picker("", selection: style) {
|
||
ForEach(GaugeDisplayStyle.allCases) { s in
|
||
Text(s.displayName).tag(s)
|
||
}
|
||
}
|
||
.pickerStyle(.menu)
|
||
.frame(width: 76)
|
||
}
|
||
|
||
Picker("", selection: pos) {
|
||
ForEach(ModulePosition.allCases) { p in
|
||
Text(p.displayName).tag(p)
|
||
}
|
||
}
|
||
.pickerStyle(.segmented)
|
||
.frame(width: 90)
|
||
}
|
||
}
|
||
|
||
// MARK: - 进程
|
||
|
||
private var processGroup: some View {
|
||
VStack(alignment: .leading, spacing: 6) {
|
||
Text("进程列表")
|
||
.font(.system(size: 11, weight: .semibold))
|
||
.foregroundColor(.secondary)
|
||
.padding(.leading, 4)
|
||
|
||
HStack {
|
||
Text("显示数量")
|
||
.font(.system(size: 12))
|
||
Spacer()
|
||
Picker("", selection: $config.processCount) {
|
||
ForEach([3, 5, 8, 10], id: \.self) { n in
|
||
Text("\(n) 个").tag(n)
|
||
}
|
||
}
|
||
.pickerStyle(.segmented)
|
||
.frame(width: 160)
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - 其他
|
||
|
||
private var otherGroup: some View {
|
||
VStack(alignment: .leading, spacing: 8) {
|
||
Text("其他")
|
||
.font(.system(size: 11, weight: .semibold))
|
||
.foregroundColor(.secondary)
|
||
.padding(.leading, 4)
|
||
|
||
HStack {
|
||
Text("刷新间隔")
|
||
.font(.system(size: 12))
|
||
Spacer()
|
||
Picker("", selection: Binding<Double>(
|
||
get: { config.refreshInterval },
|
||
set: { newValue in
|
||
config.refreshInterval = newValue
|
||
NotificationCenter.default.post(
|
||
name: .refreshIntervalChanged, object: newValue
|
||
)
|
||
}
|
||
)) {
|
||
Text("0.5s").tag(0.5)
|
||
Text("1.0s").tag(1.0)
|
||
Text("1.5s").tag(1.5)
|
||
Text("2.0s").tag(2.0)
|
||
Text("3.0s").tag(3.0)
|
||
}
|
||
.pickerStyle(.segmented)
|
||
.frame(width: 180)
|
||
}
|
||
|
||
HStack {
|
||
Text("字体大小")
|
||
.font(.system(size: 12))
|
||
.frame(width: 52, alignment: .leading)
|
||
Slider(value: $config.fontSize, in: 9...16, step: 1) {
|
||
Text("")
|
||
}
|
||
.frame(width: 130)
|
||
Text(String(format: "%.0f", config.fontSize))
|
||
.font(.system(size: 11, design: .monospaced))
|
||
.foregroundColor(.secondary)
|
||
.frame(width: 38, alignment: .trailing)
|
||
}
|
||
|
||
HStack {
|
||
Text("窗口宽度")
|
||
.font(.system(size: 12))
|
||
.frame(width: 52, alignment: .leading)
|
||
Slider(value: $config.windowWidth, in: 240...500) {
|
||
Text("")
|
||
} onEditingChanged: { editing in
|
||
if !editing { applyWindowSize() }
|
||
}
|
||
.frame(width: 130)
|
||
Text(String(format: "%.0f", config.windowWidth))
|
||
.font(.system(size: 11, design: .monospaced))
|
||
.foregroundColor(.secondary)
|
||
.frame(width: 38, alignment: .trailing)
|
||
}
|
||
|
||
HStack {
|
||
Text("窗口高度")
|
||
.font(.system(size: 12))
|
||
.frame(width: 52, alignment: .leading)
|
||
Slider(value: $config.windowHeight, in: 200...700) {
|
||
Text("")
|
||
} onEditingChanged: { editing in
|
||
if !editing { applyWindowSize() }
|
||
}
|
||
.frame(width: 130)
|
||
Text(String(format: "%.0f", config.windowHeight))
|
||
.font(.system(size: 11, design: .monospaced))
|
||
.foregroundColor(.secondary)
|
||
.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))
|
||
Spacer()
|
||
Toggle("", isOn: $config.gaugeAnimationEnabled)
|
||
.toggleStyle(.switch)
|
||
.scaleEffect(0.7)
|
||
.frame(width: 38)
|
||
}
|
||
|
||
HStack {
|
||
Text("文字动画")
|
||
.font(.system(size: 12))
|
||
Spacer()
|
||
Toggle("", isOn: $config.textAnimationEnabled)
|
||
.toggleStyle(.switch)
|
||
.scaleEffect(0.7)
|
||
.frame(width: 38)
|
||
}
|
||
}
|
||
}
|
||
|
||
private func applyWindowSize() {
|
||
NotificationCenter.default.post(
|
||
name: .resizeMonitorWindow,
|
||
object: NSSize(width: config.windowWidth, height: config.windowHeight)
|
||
)
|
||
}
|
||
|
||
private func closeWindow() {
|
||
NSApp.keyWindow?.close()
|
||
}
|
||
}
|