Monitor/Sources/FloatingMonitor/Views/GaugeView.swift
JOJO 4ab9a7b28c Initial: FloatingMonitor — macOS 悬浮系统监控
- 实时 CPU/GPU/内存/Swap 监控 + Top 进程
- 5 种计量器样式(圆环/条状/纯文本/扇形/紧凑)
- 半透明悬浮窗 + 菜单栏图标控制
- 独立设置窗口,每模块可指定行号+列位布局
- 窗口宽高/透明度/字体全局可调
2026-05-05 21:26:44 +08:00

218 lines
7.0 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import SwiftUI
// MARK: - GaugeDisplayStyle
struct StyledGauge: View {
let title: String
let icon: String
let value: Double // 0-100
let unit: String // "%" / "GB"
let detail: String // "3.2 / 16 GB"
let style: GaugeDisplayStyle
let fontSize: Double
//
private var gaugeColor: Color {
if value < 40 { return .green }
if value < 70 { return .yellow }
if value < 85 { return .orange }
return .red
}
var body: some View {
VStack(alignment: .leading, spacing: 6) {
//
HStack(spacing: 6) {
Image(systemName: icon)
.font(.system(size: fontSize))
.foregroundColor(.secondary)
Text(title)
.font(.system(size: fontSize, weight: .medium))
.foregroundColor(.secondary)
Spacer()
}
//
switch style {
case .bar:
barView
case .ring:
ringView
case .text:
textView
case .pie:
pieView
case .compact:
compactView
}
}
}
// MARK: -
private var barView: some View {
VStack(spacing: 2) {
GeometryReader { geo in
ZStack(alignment: .leading) {
Capsule()
.fill(Color.white.opacity(0.12))
.frame(height: 8)
Capsule()
.fill(
LinearGradient(
colors: [gaugeColor.opacity(0.8), gaugeColor],
startPoint: .leading,
endPoint: .trailing
)
)
.frame(width: max(geo.size.width * CGFloat(value / 100), 4), height: 8)
}
}
.frame(height: 8)
HStack {
Text(String(format: "%.1f%@", value, unit))
.font(.system(size: fontSize + 2, weight: .bold, design: .monospaced))
.foregroundColor(.primary)
Spacer()
Text(detail)
.font(.system(size: fontSize - 2))
.foregroundColor(.secondary)
.lineLimit(1)
}
}
}
// MARK: -
private var ringView: some View {
HStack(spacing: 10) {
ZStack {
Circle()
.stroke(Color.white.opacity(0.12), lineWidth: 5)
.frame(width: 48, height: 48)
Circle()
.trim(from: 0, to: CGFloat(value / 100))
.stroke(
AngularGradient(
colors: [gaugeColor.opacity(0.7), gaugeColor, gaugeColor.opacity(0.9)],
center: .center
),
style: StrokeStyle(lineWidth: 5, lineCap: .round)
)
.rotationEffect(.degrees(-90))
.frame(width: 48, height: 48)
Text(String(format: "%.0f", value))
.font(.system(size: fontSize - 1, weight: .bold, design: .monospaced))
.foregroundColor(.primary)
}
VStack(alignment: .leading, spacing: 2) {
Text(String(format: "%.1f%@", value, unit))
.font(.system(size: fontSize + 2, weight: .bold, design: .monospaced))
.foregroundColor(.primary)
Text(detail)
.font(.system(size: fontSize - 2))
.foregroundColor(.secondary)
.lineLimit(1)
}
}
}
// MARK: -
private var textView: some View {
HStack {
Text(String(format: "%.1f%@", value, unit))
.font(.system(size: fontSize + 6, weight: .bold, design: .monospaced))
.foregroundColor(gaugeColor)
Spacer()
Text(detail)
.font(.system(size: fontSize - 2))
.foregroundColor(.secondary)
.lineLimit(1)
}
}
// MARK: -
private var pieView: some View {
HStack(spacing: 10) {
ZStack {
Circle()
.fill(Color.white.opacity(0.05))
.frame(width: 44, height: 44)
PieSlice(endAngle: .degrees(360 * value / 100))
.fill(gaugeColor.opacity(0.85))
.frame(width: 42, height: 42)
.clipShape(Circle())
Text(String(format: "%.0f", value))
.font(.system(size: fontSize - 2, weight: .bold, design: .monospaced))
.foregroundColor(.white)
}
VStack(alignment: .leading, spacing: 2) {
Text(String(format: "%.1f%@", value, unit))
.font(.system(size: fontSize + 2, weight: .bold, design: .monospaced))
.foregroundColor(.primary)
Text(detail)
.font(.system(size: fontSize - 2))
.foregroundColor(.secondary)
.lineLimit(1)
}
}
}
// MARK: -
private var compactView: some View {
VStack(spacing: 4) {
GeometryReader { geo in
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: 4)
.fill(Color.white.opacity(0.1))
.frame(height: 14)
RoundedRectangle(cornerRadius: 4)
.fill(gaugeColor)
.frame(width: max(geo.size.width * CGFloat(value / 100), 3), height: 14)
}
}
.frame(height: 14)
HStack {
Text(String(format: "%.1f%@", value, unit))
.font(.system(size: fontSize, weight: .bold, design: .monospaced))
.foregroundColor(.primary)
Spacer()
Text(detail)
.font(.system(size: fontSize - 2))
.foregroundColor(.secondary)
.lineLimit(1)
}
}
}
}
// MARK: - Shape
struct PieSlice: Shape {
var endAngle: Angle
func path(in rect: CGRect) -> Path {
var path = Path()
let center = CGPoint(x: rect.midX, y: rect.midY)
let radius = min(rect.width, rect.height) / 2
path.move(to: center)
path.addArc(center: center, radius: radius,
startAngle: .degrees(-90),
endAngle: endAngle - .degrees(90),
clockwise: false)
path.closeSubpath()
return path
}
}