mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-03 01:23:43 +00:00
feat: show runtime status dots in menu bar
This commit is contained in:
@@ -62,7 +62,7 @@ struct ShadowsocksXNG2App: App {
|
||||
ProxyStatusMenu(
|
||||
control: proxyControl, statusDisplay: statusDisplay)
|
||||
} label: {
|
||||
StatusMenuLabel(control: proxyControl)
|
||||
StatusMenuLabel(control: proxyControl, statusDisplay: statusDisplay)
|
||||
}
|
||||
// .menuBarExtraStyle(.window)
|
||||
|
||||
@@ -148,23 +148,6 @@ private final class NSAppWindowActivationApplier: WindowActivationPolicyApplying
|
||||
}
|
||||
}
|
||||
|
||||
/// 状态菜单的标签视图(状态栏图标)。图标为静态 SF Symbol,不随代理状态
|
||||
/// 变化,故不观察 control——control 仅承载启动 resync。挂载时机与菜单内容
|
||||
/// 视图不同:内容视图首次点开才创建,而标签在启动时即挂载——启动 resync
|
||||
/// 只能挂在这里,挂在菜单内容上等于从不执行。主窗口的启动呈现由 Window
|
||||
/// scene 的 defaultLaunchBehavior 负责,与本任务无时序耦合。
|
||||
private struct StatusMenuLabel: View {
|
||||
let control: ProxyControlWorkflow
|
||||
|
||||
var body: some View {
|
||||
Image(systemName: "paperplane.fill")
|
||||
.accessibilityLabel("ShadowsocksX-NG2")
|
||||
.task {
|
||||
await control.resyncOnLaunch()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 组合根装配:目录提交协调器与全部生产运行时适配器只在此接线一次,各 scene
|
||||
/// 不再重复设置提交回调;目录工作流的实现依赖经 CatalogWorkflowDependencies
|
||||
/// 显式装配(workflow 不自行创建生产默认实现);Legacy 导入后的 2.0 运行时
|
||||
|
||||
@@ -64,10 +64,7 @@ struct RuntimeControlCard: View {
|
||||
|
||||
private func statusRow(_ row: StatusCardModel.Row) -> some View {
|
||||
HStack(spacing: 6) {
|
||||
Circle()
|
||||
.fill(statusColor(row.tone))
|
||||
.frame(width: 8, height: 8)
|
||||
.accessibilityHidden(true)
|
||||
RuntimeStatusDot(tone: row.tone, diameter: 8)
|
||||
Text(row.text)
|
||||
.font(.body.weight(.medium))
|
||||
.foregroundStyle(.secondary)
|
||||
@@ -75,15 +72,6 @@ struct RuntimeControlCard: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func statusColor(_ tone: StatusCardModel.Tone) -> Color {
|
||||
switch tone {
|
||||
case .positive: .green
|
||||
case .attention: .orange
|
||||
case .failure: .red
|
||||
case .neutral: .secondary
|
||||
}
|
||||
}
|
||||
|
||||
private var agentBinding: Binding<Bool> {
|
||||
Binding(
|
||||
get: { control.snapshot.agentIntentEnabled },
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import AppKit
|
||||
import SwiftUI
|
||||
|
||||
/// 菜单栏面板代理状态卡。状态行经 StatusDisplayModel 的瞬态门控呈现
|
||||
@@ -36,8 +37,21 @@ struct StatusCardView: View {
|
||||
.lineLimit(2)
|
||||
.help(detail.text)
|
||||
}
|
||||
}
|
||||
|
||||
private func color(_ tone: StatusCardModel.Tone) -> Color {
|
||||
/// 首页与状态栏共用圆点及色调映射,尺寸由显示位置决定。
|
||||
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
|
||||
@@ -46,3 +60,39 @@ struct StatusCardView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 标签在启动时即挂载;启动 resync 必须留在此处,不能移入首次点开才创建的菜单内容。
|
||||
struct StatusMenuLabel: View {
|
||||
let control: ProxyControlWorkflow
|
||||
let statusDisplay: StatusDisplayModel
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
@Environment(\.displayScale) private var displayScale
|
||||
|
||||
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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user