✨ 新增高亮模式:所有文字变为对应指标颜色(绿→橙→红),无视颜色反转
This commit is contained in:
parent
38a1956c7f
commit
c4cc9c7db5
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,4 +2,5 @@
|
||||
dmg_build/
|
||||
icon_build/
|
||||
*.dmg
|
||||
*.app
|
||||
.DS_Store
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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("隐藏窗口(点击菜单栏图标重新打开)")
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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))
|
||||
|
||||
84
build_dmg.sh
Normal file
84
build_dmg.sh
Normal file
@ -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'
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleName</key>
|
||||
<string>FloatingMonitor</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Floating Monitor</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.floatingmonitor.app</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0.0</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.0</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>FloatingMonitor</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>14.0</string>
|
||||
<key>LSUIElement</key>
|
||||
<true/>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
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"
|
||||
Loading…
Reference in New Issue
Block a user