diff --git a/.gitignore b/.gitignore index 5128e82..1685b05 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ dmg_build/ icon_build/ *.dmg +*.app .DS_Store diff --git a/Sources/FloatingMonitor/Configuration.swift b/Sources/FloatingMonitor/Configuration.swift index 316c03d..847cbb5 100644 --- a/Sources/FloatingMonitor/Configuration.swift +++ b/Sources/FloatingMonitor/Configuration.swift @@ -119,6 +119,9 @@ final class Configuration: ObservableObject { @Published var invertTextColor: Bool { didSet { save("invertTextColor", invertTextColor) } } + @Published var highlightMode: Bool { + didSet { save("highlightMode", highlightMode) } + } @Published var gaugeAnimationEnabled: Bool { didSet { save("gaugeAnimationEnabled", gaugeAnimationEnabled) } @@ -204,6 +207,7 @@ final class Configuration: ObservableObject { windowHeight = defaults.object(forKey: "windowHeight") as? Double ?? 300 backdropEnabled = defaults.object(forKey: "backdropEnabled") as? Bool ?? true invertTextColor = defaults.object(forKey: "invertTextColor") as? Bool ?? false + highlightMode = defaults.object(forKey: "highlightMode") as? Bool ?? false gaugeAnimationEnabled = defaults.object(forKey: "gaugeAnimationEnabled") as? Bool ?? true textAnimationEnabled = defaults.object(forKey: "textAnimationEnabled") as? Bool ?? true diff --git a/Sources/FloatingMonitor/ContentView.swift b/Sources/FloatingMonitor/ContentView.swift index db2dcf7..90e4975 100644 --- a/Sources/FloatingMonitor/ContentView.swift +++ b/Sources/FloatingMonitor/ContentView.swift @@ -33,7 +33,7 @@ struct ContentView: View { } .frame(minWidth: 240, idealWidth: 280, maxWidth: 400, minHeight: 200, idealHeight: 480, maxHeight: 700) - .environment(\.colorScheme, config.invertTextColor ? .dark : .light) + .environment(\.colorScheme, (config.invertTextColor && !config.highlightMode) ? .dark : .light) .onAppear { monitor.interval = config.refreshInterval monitor.topN = config.processCount @@ -58,18 +58,18 @@ struct ContentView: View { Text("Monitor") .font(.system(size: config.fontSize, weight: .semibold)) - .foregroundColor(.primary.opacity(0.85)) + .foregroundColor(config.highlightMode ? .accentColor : .primary.opacity(0.85)) Spacer() Text(timeString) .font(.system(size: 10, design: .monospaced)) - .foregroundColor(.secondary.opacity(0.5)) + .foregroundColor(config.highlightMode ? .accentColor : .secondary.opacity(0.5)) Button(action: { windowController?.hide() }) { Image(systemName: "xmark") .font(.system(size: 11, weight: .medium)) - .foregroundColor(.secondary.opacity(0.5)) + .foregroundColor(config.highlightMode ? .accentColor : .secondary.opacity(0.5)) } .buttonStyle(.plain) .help("隐藏窗口(点击菜单栏图标重新打开)") diff --git a/Sources/FloatingMonitor/Views/GaugeView.swift b/Sources/FloatingMonitor/Views/GaugeView.swift index fd39327..52c812e 100644 --- a/Sources/FloatingMonitor/Views/GaugeView.swift +++ b/Sources/FloatingMonitor/Views/GaugeView.swift @@ -14,6 +14,8 @@ struct StyledGauge: View { var animateGraphic: Bool = true var animateText: Bool = true + @ObservedObject private var config = Configuration.shared + private var progress: Double { maxValue > 0 ? min(value / maxValue, 1.0) : 0 } @@ -25,6 +27,15 @@ struct StyledGauge: View { return .red } + /// 高亮模式下所有文字用 gaugeColor,否则用系统颜色 + private var textColor: Color { + config.highlightMode ? gaugeColor : .primary + } + + private var secondaryColor: Color { + config.highlightMode ? gaugeColor : .secondary + } + /// 文字动画:条件应用 contentTransition @ViewBuilder private func animatedNumber(_ text: Text) -> some View { @@ -48,10 +59,10 @@ struct StyledGauge: View { HStack(spacing: 6) { Image(systemName: icon) .font(.system(size: fontSize)) - .foregroundColor(.secondary) + .foregroundColor(secondaryColor) Text(title) .font(.system(size: fontSize, weight: .medium)) - .foregroundColor(.secondary) + .foregroundColor(secondaryColor) Spacer() } @@ -86,11 +97,11 @@ struct StyledGauge: View { HStack { animatedNumber(Text(String(format: "%.1f%@", value, unit)) .font(.system(size: fontSize + 2, weight: .bold, design: .monospaced)) - .foregroundColor(.primary)) + .foregroundColor(textColor)) Spacer() Text(detail) .font(.system(size: fontSize - 2)) - .foregroundColor(.secondary) + .foregroundColor(secondaryColor) .lineLimit(1) } .animation(textAnim, value: value) @@ -117,17 +128,17 @@ struct StyledGauge: View { animatedNumber(Text(String(format: "%.0f", value)) .font(.system(size: fontSize - 1, weight: .bold, design: .monospaced)) - .foregroundColor(.primary)) + .foregroundColor(textColor)) } .animation(textAnim, value: value) VStack(alignment: .leading, spacing: 2) { animatedNumber(Text(String(format: "%.1f%@", value, unit)) .font(.system(size: fontSize + 2, weight: .bold, design: .monospaced)) - .foregroundColor(.primary)) + .foregroundColor(textColor)) Text(detail) .font(.system(size: fontSize - 2)) - .foregroundColor(.secondary) + .foregroundColor(secondaryColor) .lineLimit(1) } .animation(textAnim, value: value) @@ -145,7 +156,7 @@ struct StyledGauge: View { Spacer() Text(detail) .font(.system(size: fontSize - 2)) - .foregroundColor(.secondary) + .foregroundColor(secondaryColor) .lineLimit(1) } } @@ -174,10 +185,10 @@ struct StyledGauge: View { VStack(alignment: .leading, spacing: 2) { animatedNumber(Text(String(format: "%.1f%@", value, unit)) .font(.system(size: fontSize + 2, weight: .bold, design: .monospaced)) - .foregroundColor(.primary)) + .foregroundColor(textColor)) Text(detail) .font(.system(size: fontSize - 2)) - .foregroundColor(.secondary) + .foregroundColor(secondaryColor) .lineLimit(1) } .animation(textAnim, value: value) diff --git a/Sources/FloatingMonitor/Views/ProcessListView.swift b/Sources/FloatingMonitor/Views/ProcessListView.swift index 400ffd5..d147b3e 100644 --- a/Sources/FloatingMonitor/Views/ProcessListView.swift +++ b/Sources/FloatingMonitor/Views/ProcessListView.swift @@ -7,16 +7,23 @@ struct ProcessListView: View { let isMemory: Bool // true = 显示内存,false = 显示 CPU% let fontSize: Double + @ObservedObject private var config = Configuration.shared + + /// 高亮模式下标题/图标/表头的主题色(Top 进程典型为橙色) + private var headerColor: Color { + config.highlightMode ? .orange : .secondary + } + var body: some View { VStack(alignment: .leading, spacing: 4) { // 标题 HStack(spacing: 5) { Image(systemName: icon) .font(.system(size: fontSize)) - .foregroundColor(.secondary) + .foregroundColor(headerColor) Text(title) .font(.system(size: fontSize, weight: .medium)) - .foregroundColor(.secondary) + .foregroundColor(headerColor) Spacer() } @@ -24,11 +31,11 @@ struct ProcessListView: View { HStack(spacing: 0) { Text("进程") .font(.system(size: fontSize - 2)) - .foregroundColor(.secondary.opacity(0.6)) + .foregroundColor(headerColor.opacity(0.6)) .frame(maxWidth: .infinity, alignment: .leading) Text(isMemory ? "内存" : "CPU") .font(.system(size: fontSize - 2)) - .foregroundColor(.secondary.opacity(0.6)) + .foregroundColor(headerColor.opacity(0.6)) .frame(width: 52, alignment: .trailing) } .padding(.horizontal, 4) @@ -48,7 +55,9 @@ struct ProcessListView: View { // 进程名 Text(proc.name.components(separatedBy: "/").last ?? proc.name) .font(.system(size: fontSize - 1, design: .monospaced)) - .foregroundColor(.primary.opacity(0.85)) + .foregroundColor(config.highlightMode + ? (isMemory ? memoryColor(proc.memoryBytes) : cpuColor(proc.cpuPercent)).opacity(0.7) + : .primary.opacity(0.85)) .lineLimit(1) .truncationMode(.tail) diff --git a/Sources/FloatingMonitor/Views/SettingsView.swift b/Sources/FloatingMonitor/Views/SettingsView.swift index 223d1f2..070f9e2 100644 --- a/Sources/FloatingMonitor/Views/SettingsView.swift +++ b/Sources/FloatingMonitor/Views/SettingsView.swift @@ -289,6 +289,17 @@ struct SettingsView: View { .frame(width: 38) } + HStack { + Text("高亮模式") + .font(.system(size: 12)) + Spacer() + Toggle("", isOn: $config.highlightMode) + .toggleStyle(.switch) + .scaleEffect(0.7) + .frame(width: 38) + } + .help("开启后所有文字变为对应指标颜色(绿→橙→红),无视颜色反转设定") + HStack { Text("图形动画") .font(.system(size: 12)) diff --git a/build_dmg.sh b/build_dmg.sh new file mode 100644 index 0000000..dc6f1b5 --- /dev/null +++ b/build_dmg.sh @@ -0,0 +1,84 @@ +#!/bin/bash +set -e + +PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)" +BUILD_DIR="$PROJECT_DIR/.build/release" +APP_NAME="FloatingMonitor" +APP_BUNDLE="$PROJECT_DIR/$APP_NAME.app" +DMG_NAME="$PROJECT_DIR/$APP_NAME.dmg" +STAGING="$PROJECT_DIR/dmg_staging" + +echo "📦 构建 DMG: $DMG_NAME" + +# 1. Release 编译 +echo "🔨 Step 1/5: swift build -c release..." +cd "$PROJECT_DIR" +swift build -c release + +# 2. 创建 .app bundle 结构 +echo "📂 Step 2/5: 创建 .app bundle..." +rm -rf "$APP_BUNDLE" +mkdir -p "$APP_BUNDLE/Contents/MacOS" +mkdir -p "$APP_BUNDLE/Contents/Resources" + +# 3. 复制可执行文件 +echo "📋 Step 3/5: 复制可执行文件..." +cp "$BUILD_DIR/$APP_NAME" "$APP_BUNDLE/Contents/MacOS/" + +# 4. 创建 Info.plist +echo "📝 Step 4/5: 创建 Info.plist..." +cat > "$APP_BUNDLE/Contents/Info.plist" << 'PLIST' + + + + + CFBundleName + FloatingMonitor + CFBundleDisplayName + Floating Monitor + CFBundleIdentifier + com.floatingmonitor.app + CFBundleVersion + 1.0.0 + CFBundleShortVersionString + 1.0.0 + CFBundleExecutable + FloatingMonitor + CFBundlePackageType + APPL + LSMinimumSystemVersion + 14.0 + LSUIElement + + NSHighResolutionCapable + + + +PLIST + +# 复制 Resources +if [ -d "$PROJECT_DIR/Sources/Resources" ]; then + cp -R "$PROJECT_DIR/Sources/Resources/"* "$APP_BUNDLE/Contents/Resources/" 2>/dev/null || true +fi + +# 5. 创建 DMG +echo "💿 Step 5/5: 创建 DMG..." +rm -rf "$STAGING" +mkdir -p "$STAGING" +cp -R "$APP_BUNDLE" "$STAGING/" + +# 创建 Applications 快捷方式 +ln -s /Applications "$STAGING/Applications" + +rm -f "$DMG_NAME" +hdiutil create -volname "$APP_NAME" \ + -srcfolder "$STAGING" \ + -ov -format UDZO \ + "$DMG_NAME" + +# 清理 +rm -rf "$STAGING" + +echo "" +echo "✅ DMG 创建成功: $DMG_NAME" +ls -lh "$DMG_NAME"