菜单栏新增双行居中风格:标签比数值宽时数值不再偏到右边

This commit is contained in:
gentpan committed 2026-09-21 20:37:04 +05:00
1 parent 2efebb5008
commit d05b02be4f
8 files changed
+78 -50

No files matched your search

@@ -334,6 +334,7 @@ npm 缓存 npm Cache
去批准 Approve
去授权 Grant Access
双行圆点 Dots
双行居中 Stacked, Centered
双行文字 Stacked Text
双行箭头 Arrows
反馈问题 Report a Problem
@@ -382,6 +383,7 @@ npm 缓存 npm Cache
合盖运行时散热变差,请勿放入包中。使用电池且电量低于 {}% 时会自动关闭。 Cooling is worse with the lid closed, so don't put your Mac in a bag. Turns off automatically on battery below {}%.
合盖运行设置 Lid Mode
合盖运行需要修改系统睡眠设置,需安装辅助工具(管理员授权一次)。 Lid mode changes system sleep settings and needs the helper (one-time admin approval).
同双行文字,但两行各自居中;标签比数值宽时(风扇、温度)不会偏到一边 Same as stacked, but both lines are centered, so a value narrower than its label (fan, temperature) is not pushed to one side
同步 Sync
同步失败 Sync failed
同步的内容:菜单栏项目与风格、刷新频率、外观与语言、详情弹窗的区块、连接探测、通知、快捷键、风扇安全温度、合盖电量下限。当前页面、辅助工具与历史记录留在本机。 What syncs: menu bar items and styles, refresh rate, appearance and language, popover sections, connection probe, notifications, hotkeys, fan safety temperature and lid-mode battery floor. The current page, the helper and history stay on this Mac.
@@ -450,7 +452,7 @@ npm 缓存 npm Cache
寿命按厂商估算的已用比例计算;写入量越大消耗越快,日常使用通常可用很多年 Life is based on the vendor's wear estimate. Heavy writes wear it faster; typical use lasts many years.
将清理 {} 个项目,共 {}。{} {} items, {} total, will be cleaned. {}
将移到废纸篓 Will move to Trash
小标签在上、数值在下,最紧凑 Small label above the value; most compact
小标签在上、数值在下,两行靠右对齐,数值的右边不会跳 Small label on top, value below, both right-aligned so the value's right edge stays put
尚未检查 Not checked yet
屏幕保持常亮 Keep display on
屏幕可按设置关闭,下载、编译等后台任务继续运行 The display can turn off as usual while downloads and builds keep running
@@ -243,10 +243,11 @@ enum MenuBarRenderer {
let text = Format.percent(fraction)
let alert = colorizeHighLoad && fraction <= Metrics.lowBatteryLevel
let label = MenuBarItem.battery.menuBarLabel
let stacked = stackedText(label: label, value: text, sample: "100%", alert: alert)
let stacked = stackedText(label: label, value: text, sample: "100%", alert: alert,
centered: style == .stackedCenter)
var segment: Segment
switch style {
case .stacked: segment = stacked
case .stacked, .stackedCenter: segment = stacked
case .inline: segment = inlineText(label: label, value: text, sample: "100%", alert: alert)
case .icon:
let symbol = reading.batteryCharging ? "battery.100.bolt" : batterySymbol(fraction)
@@ -283,10 +284,11 @@ enum MenuBarRenderer {
let fraction = min(1, max(0, value ?? 0))
let text = value.map { Format.percent($0) } ?? "—"
let alert = colorizeHighLoad && fraction >= Metrics.highLevel
let stacked = stackedText(label: item.menuBarLabel, value: text, sample: "100%", alert: alert)
let stacked = stackedText(label: item.menuBarLabel, value: text, sample: "100%", alert: alert,
centered: style == .stackedCenter)
switch style {
case .stacked:
case .stacked, .stackedCenter:
return stacked
case .inline:
return inlineText(label: item.menuBarLabel, value: text, sample: "100%", alert: alert)
@@ -312,7 +314,8 @@ enum MenuBarRenderer {
switch style {
case .inline: inlineText(label: item.menuBarLabel, value: value, sample: sample, alert: false)
case .icon: combine([symbolSegment(item.symbol), inlineValue(value, sample: sample, alert: false)])
default: stackedText(label: item.menuBarLabel, value: value, sample: sample, alert: false)
default: stackedText(label: item.menuBarLabel, value: value, sample: sample, alert: false,
centered: style == .stackedCenter)
}
}
@@ -323,14 +326,24 @@ enum MenuBarRenderer {
}
/// 颜色保持动态(labelColor / secondaryLabelColor),在绘制时才按菜单栏外观解析
private static func stackedText(label: String, value: String, sample: String, alert: Bool) -> Segment {
/// 两行文字:宽度按标签、数值与样本里最宽的算,所以数值变长也不会挤动旁边的项目。
/// `centered` 时两行各自居中,标签比数值宽时(风扇、温度)数值不会贴在右边
private static func stackedText(label: String, value: String, sample: String, alert: Bool,
centered: Bool = false) -> Segment {
let labelAttributes: [NSAttributedString.Key: Any] = [.font: Metrics.labelFont, .foregroundColor: NSColor.secondaryLabelColor]
let valueAttributes: [NSAttributedString.Key: Any] = [.font: Metrics.stackedValueFont, .foregroundColor: foreground(alert)]
let width = ceil(max(textWidth(label, labelAttributes), textWidth(value, valueAttributes), textWidth(sample, valueAttributes)))
return Segment(width: width) { rect in
let base = baselineOrigin(in: rect)
drawText(label, labelAttributes, rightEdge: rect.maxX, baseline: base + Metrics.upperBaseline)
drawText(value, valueAttributes, rightEdge: rect.maxX, baseline: base + Metrics.lowerBaseline)
let rows = [(label, labelAttributes, base + Metrics.upperBaseline),
(value, valueAttributes, base + Metrics.lowerBaseline)]
for (text, attributes, baseline) in rows {
if centered {
drawText(text, attributes, centerX: rect.midX, baseline: baseline)
} else {
drawText(text, attributes, rightEdge: rect.maxX, baseline: baseline)
}
}
}
}
@@ -363,6 +376,13 @@ enum MenuBarRenderer {
string.draw(with: NSRect(x: rightEdge - width, y: baseline, width: width, height: 0))
}
/// 居中绘制:起点取整像素,文字才不会发虚
private static func drawText(_ text: String, _ attributes: [NSAttributedString.Key: Any], centerX: CGFloat, baseline: CGFloat) {
let string = NSAttributedString(string: text, attributes: attributes)
let width = string.size().width
string.draw(with: NSRect(x: (centerX - width / 2).rounded(), y: baseline, width: width, height: 0))
}
/// 刘海屏的菜单栏更高,两行内容整体在按钮内垂直居中
private static func baselineOrigin(in rect: NSRect) -> CGFloat {
rect.midY - Metrics.barHeight / 2
@@ -472,8 +472,20 @@ private struct StyleCard: View {
let action: () -> Void
@State private var hovering = false
/// 两种双行风格的差别只在标签比数值宽时看得出来,预览里用风扇(标签宽、数值短)代替 GPU
private var isStacked: Bool { style == .stacked || style == .stackedCenter }
private var previewItems: [MenuBarItem] { isStacked ? [.cpu, .memory, .fan] : [.cpu, .memory, .gpu] }
private var previewReading: MenuBarReading {
guard isStacked else { return .sample }
var reading = MenuBarReading.sample
reading.fanRPM = 0
return reading
}
var body: some View {
let sample = MenuBarRenderer.image(reading: .sample, items: [.cpu, .memory, .gpu],
let sample = MenuBarRenderer.image(reading: previewReading, items: previewItems,
style: { _ in style }, networkStyle: model.settings.networkStyle,
colorizeHighLoad: false, fahrenheit: model.settings.useFahrenheit)
Button(action: action) {
@@ -184,14 +184,14 @@ public enum ProbeTarget: String, CaseIterable, Identifiable, Sendable {
/// 菜单栏风格:整体选一种套用到所有指标,个别指标可以单独指定(AppSettings.styleOverrides);风格内部的排版保持一致
public enum MenuBarStyle: String, CaseIterable, Identifiable, Sendable {
case stacked, inline, icon, ring, pie, history, line, meter, dot
case stacked, stackedCenter, inline, icon, ring, pie, history, line, meter, dot
public var id: String { rawValue }
/// 温度、风扇没有百分比,图形类风格画不出来,只给文字类的三种
static func options(for item: MenuBarItem) -> [MenuBarStyle] {
switch item {
case .temperature, .fan: [.stacked, .inline, .icon]
case .temperature, .fan: [.stacked, .stackedCenter, .inline, .icon]
default: allCases
}
}
@@ -199,6 +199,7 @@ public enum MenuBarStyle: String, CaseIterable, Identifiable, Sendable {
var title: String {
switch self {
case .stacked: tr("双行文字")
case .stackedCenter: tr("双行居中")
case .inline: tr("单行文字")
case .icon: tr("图标")
case .ring: tr("圆环")
@@ -212,7 +213,8 @@ public enum MenuBarStyle: String, CaseIterable, Identifiable, Sendable {
var detail: String {
switch self {
case .stacked: tr("小标签在上、数值在下,最紧凑")
case .stacked: tr("小标签在上、数值在下,两行靠右对齐,数值的右边不会跳")
case .stackedCenter: tr("同双行文字,但两行各自居中;标签比数值宽时(风扇、温度)不会偏到一边")
case .inline: tr("标签与数值同一行,最易读")
case .icon: tr("用系统图标代替文字标签")
case .ring: tr("圆环表示当前占用比例")