Files

102 lines
3.2 KiB
Swift
Raw Permalink 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 AppKit
import SwiftUI
/// 菜单栏面板代理状态卡。状态行经 StatusDisplayModel 的瞬态门控呈现
/// (与首页运行控制卡同一 debounce 口径);活动目标与模式行仍从 control
/// 的整体 snapshot 直接派生。
struct StatusCardView: View {
@ObservedObject var control: ProxyControlWorkflow
let statusDisplay: StatusDisplayModel
var body: some View {
let card = StatusCardModel.card(from: control.snapshot)
row(statusDisplay.agent.row)
if let detail = statusDisplay.agent.detail {
detailLine(detail)
}
row(statusDisplay.systemProxy.row)
if let detail = statusDisplay.systemProxy.detail {
detailLine(detail)
}
Text(card.target.text)
.truncationMode(.middle)
.help(card.target.help)
.accessibilityElement(children: .ignore)
.accessibilityLabel("活动目标:\(card.target.text)")
Text("模式:\(card.modeText)").accessibilityElement(children: .combine)
}
private func row(_ row: StatusCardModel.Row) -> some View {
Text("\(row.label):\(row.text)").accessibilityElement(children: .combine)
}
private func detailLine(_ detail: StatusCardModel.DetailLine) -> some View {
Text(detail.text)
.lineLimit(2)
.help(detail.text)
}
}
/// 首页与状态栏共用圆点及色调映射,尺寸由显示位置决定。
struct RuntimeStatusDot: View {
let tone: StatusCardModel.Tone
let diameter: CGFloat
var body: some View {
Circle()
.fill(color)
.frame(width: diameter, height: diameter)
.accessibilityHidden(true)
}
private var color: Color {
switch tone {
case .positive: .green
case .attention: .orange
case .failure: .red
case .neutral: .secondary
}
}
}
/// 标签在启动时即挂载;启动 resync 必须留在此处,不能移入首次点开才创建的菜单内容。
struct StatusMenuLabel: View {
let control: ProxyControlWorkflow
let statusDisplay: StatusDisplayModel
@Environment(\.colorScheme) private var colorScheme
@Environment(\.displayScale) private var displayScale
@AppStorage("showStatusBarRuntimeDots") private var showStatusBarRuntimeDots = true
var body: some View {
Image(nsImage: statusImage)
.renderingMode(.original)
.accessibilityLabel("ShadowsocksX-NG2")
.task {
await control.resyncOnLaunch()
}
}
/// MenuBarExtra 使用单张图像承载主图标和圆点,保留圆点的原始颜色。
private var statusImage: NSImage {
let renderer = ImageRenderer(
content: HStack(spacing: 2) {
Image(systemName: "paperplane.fill")
.font(.system(size: 16))
.foregroundStyle(.primary)
.frame(width: 18, height: 18)
if showStatusBarRuntimeDots {
VStack(spacing: 5) {
RuntimeStatusDot(tone: statusDisplay.agent.row.tone, diameter: 4)
RuntimeStatusDot(tone: statusDisplay.systemProxy.row.tone, diameter: 4)
}
}
}
.environment(\.colorScheme, colorScheme))
renderer.scale = displayScale
return renderer.nsImage ?? NSImage(
systemSymbolName: "paperplane.fill", accessibilityDescription: nil) ?? NSImage(size: .zero)
}
}