diff --git a/Sources/CodexBar/MenuBarLayoutEditor.swift b/Sources/CodexBar/MenuBarLayoutEditor.swift index 73d936dda0..97c9b64803 100644 --- a/Sources/CodexBar/MenuBarLayoutEditor.swift +++ b/Sources/CodexBar/MenuBarLayoutEditor.swift @@ -362,7 +362,9 @@ struct MenuBarLayoutEditor: View { .foregroundStyle(.secondary) MenuBarLayoutPreview( layout: self.layout, - provider: self.scopedProvider, + // All-providers scope previews whichever enabled provider best fits the layout, not just the + // first one alphabetically — that one may have no session window and preview as all dashes. + candidates: self.scope == .all ? self.providers : [self.scopedProvider].compactMap(\.self), settings: self.settings, store: self.store) .frame(maxWidth: .infinity, minHeight: 30) @@ -618,33 +620,48 @@ private struct MenuBarLayoutChipLabel: View { @MainActor private struct MenuBarLayoutPreview: View { let layout: MenuBarLayout - let provider: UsageProvider? + let candidates: [UsageProvider] @Bindable var settings: SettingsStore @Bindable var store: UsageStore private let renderer = MenuBarLayoutRenderer() var body: some View { - let provider = self.provider ?? .codex - let snapshot = self.store.snapshot(for: provider) - let data = snapshot.map { self.liveData(provider: provider, snapshot: $0) } - ?? self.representativeData(provider: provider) - let icon = ProviderBrandIcon.image(for: provider) + let resolved = self.resolvedPreviewSource() let minute = Date(timeIntervalSince1970: floor(Date().timeIntervalSince1970 / 60) * 60) let rendered = self.renderer.render( layout: self.layout, - data: data, - icon: icon, + data: resolved.data, + icon: ProviderBrandIcon.image(for: resolved.provider), options: MenuBarLayoutRenderOptions( size: self.settings.menuBarLayoutSize, highContrast: self.settings.menuBarHighContrastOnInactiveDisplays, showUsed: self.settings.usageBarsShowUsed, appearanceName: "preview", isDebugApp: false, - now: minute)) + now: minute, + usageColorTarget: self.settings.menuBarUsageColorsEnabled + ? self.settings.menuBarUsageColorTarget + : nil)) MenuBarLayoutPreviewText(rendered: rendered) } + /// Real data beats sample data, and a provider that fills the layout beats one that renders it as dashes. + /// Falling back to sample data keeps the preview showing what the layout *looks like* rather than what one + /// unconfigured provider happens to lack. + private func resolvedPreviewSource() -> (provider: UsageProvider, data: MenuBarLayoutRenderData) { + let candidates = self.candidates.isEmpty ? [.codex] : self.candidates + let live = candidates.compactMap { provider -> (UsageProvider, MenuBarLayoutRenderData)? in + guard let snapshot = self.store.snapshot(for: provider) else { return nil } + return (provider, self.liveData(provider: provider, snapshot: snapshot)) + } + if let complete = live.first(where: { $0.1.populates(self.layout) }) { + return complete + } + let provider = live.first?.0 ?? candidates[0] + return (provider, self.representativeData(provider: provider)) + } + private func liveData(provider: UsageProvider, snapshot: UsageSnapshot) -> MenuBarLayoutRenderData { let now = Date() let session: RateWindow? @@ -683,7 +700,7 @@ private struct MenuBarLayoutPreview: View { let runsOut = weeklyPaceDetail?.rightLabel let sessionPace = session .flatMap { UsagePaceText.sessionDetail(provider: provider, window: $0, now: now) }? - .leftLabel + .compactLabel let cost = self.store.tokenSnapshotForCurrentProviderConfig(for: provider)?.snapshot let costToday = MenuBarLayoutCostResolver.todayCostUSD(snapshot: cost, now: now) return MenuBarLayoutRenderData( @@ -694,7 +711,7 @@ private struct MenuBarLayoutPreview: View { weekly: MenuBarLayoutRenderWindow(weekly), automatic: MenuBarLayoutRenderWindow(automatic), sessionPace: sessionPace, - weeklyPace: weeklyPaceDetail?.leftLabel, + weeklyPace: weeklyPaceDetail?.compactLabel, runsOut: runsOut, costToday: costToday.map { UsageFormatter.currencyString($0, currencyCode: cost?.currencyCode ?? "USD") @@ -723,8 +740,8 @@ private struct MenuBarLayoutPreview: View { session: MenuBarLayoutRenderWindow(session), weekly: MenuBarLayoutRenderWindow(weekly), automatic: MenuBarLayoutRenderWindow(session), - sessionPace: L("%d%% in reserve", 12), - weeklyPace: L("%d%% in deficit", 8), + sessionPace: "+\(UsageFormatter.percentString(12))", + weeklyPace: "-\(UsageFormatter.percentString(8))", runsOut: L("menu_bar_layout_sample_runs_out"), costToday: "$1.25", cost30d: "$20.00") @@ -817,8 +834,9 @@ extension MenuBarLayoutToken { case .icon: "app.dashed" case .providerName: "textformat" case .accountLabel: "person.crop.circle" - case .percent: "percent" - case .pace: "speedometer" + // Pace renders a signed percentage, so it belongs to the percent family in the palette rather than + // carrying a speedometer that implies some other unit. + case .percent, .pace: "percent" case .usageBar: "chart.bar.fill" case .resetCountdown: "timer" case .resetAbsolute: "clock" diff --git a/Sources/CodexBar/MenuBarLayoutRenderer.swift b/Sources/CodexBar/MenuBarLayoutRenderer.swift index 40d513d6cb..f3c43097a0 100644 --- a/Sources/CodexBar/MenuBarLayoutRenderer.swift +++ b/Sources/CodexBar/MenuBarLayoutRenderer.swift @@ -35,6 +35,34 @@ struct MenuBarLayoutRenderData: Hashable { let cost30d: String? } +extension MenuBarLayoutRenderData { + /// Whether every usage-bearing token in `layout` has a value to show. + /// + /// Identity and money tokens are excluded on purpose: those go missing because the user turned them off + /// (`hidePersonalInfo`, cost tracking), which is not the same as the provider having no data to preview. + func populates(_ layout: MenuBarLayout) -> Bool { + layout.lines.allSatisfy { line in + line.allSatisfy { token in + switch token { + case let .percent(window): + MenuBarLayoutRenderer.window(window, data: self) != nil + case let .pace(window): + MenuBarLayoutRenderer.pace(window, data: self) != nil + case .usageBar: + self.automatic != nil + case .resetCountdown, .resetAbsolute: + self.automatic?.resetsAt != nil || self.automatic?.resetDescription != nil + case .runsOut: + self.runsOut != nil + case .icon, .providerName, .accountLabel, .costToday, .cost30d, + .separatorDot, .separatorPipe, .space: + true + } + } + } + } +} + struct MenuBarLayoutRenderOptions: Hashable { let size: MenuBarLayoutSize let highContrast: Bool @@ -43,6 +71,8 @@ struct MenuBarLayoutRenderOptions: Hashable { let isDebugApp: Bool /// Minute-granularity clock. Countdown tokens refresh without invalidating cached titles every tick. let now: Date + /// Which tokens carry the usage tint, or `nil` when color-coded usage is off. + let usageColorTarget: MenuBarUsageColorTarget? } struct MenuBarLayoutRenderKey: Hashable { @@ -97,9 +127,18 @@ final class MenuBarLayoutRenderer { private struct TokenStyle { let font: NSFont - let foregroundColor: NSColor + let iconTint: NSColor + /// `true` when `iconTint` is a usage color rather than the dynamic label color, which means the + /// attachment has to stop being a template image or AppKit repaints it back to the label color. + let iconTintIsUsageColor: Bool let iconHeight: CGFloat let attributes: [NSAttributedString.Key: Any] + /// Attributes for tokens whose value the usage tint restates. Identical to `attributes` unless the + /// color target singles those tokens out. + let usageAttributes: [NSAttributedString.Key: Any] + /// Windows that already have a `.percent` token somewhere in the strip. A `.pace` token for the same + /// window drops its prefix rather than repeating a label the strip already carries. + let windowsLabeledByPercentToken: Set } private let cache: MenuBarLayoutTitleCache @@ -134,7 +173,14 @@ final class MenuBarLayoutRenderer { { let isStacked = layout.lines.count == 2 let font = NSFont.systemFont(ofSize: Self.fontSize(size: options.size, isStacked: isStacked)) - let foregroundColor = options.highContrast ? NSColor.labelColor : NSColor.controlTextColor + let baseColor = options.highContrast ? NSColor.labelColor : NSColor.controlTextColor + // The strip has no single meter to read, so the tint follows whichever window the layout would show + // first. `automatic` is what the default layout renders; session/weekly cover strips that skip it. + let usageTint = options.usageColorTarget == nil + ? nil + : MenuBarUsageTint.color( + forUsedPercent: (data.automatic ?? data.session ?? data.weekly)?.usedPercent) + let foregroundColor = options.usageColorTarget == .everything ? usageTint ?? baseColor : baseColor let paragraphStyle = NSMutableParagraphStyle() if isStacked { paragraphStyle.minimumLineHeight = 9.5 @@ -149,6 +195,21 @@ final class MenuBarLayoutRenderer { if isStacked { attributes[.baselineOffset] = Self.stackedBaselineOffset } + var usageAttributes = attributes + if options.usageColorTarget == .usage, let usageTint { + usageAttributes[.foregroundColor] = usageTint + } + // Every target tints the icon: it is the one token that always reads as "this provider's usage", so + // leaving it uncolored while the numbers beside it are colored looks like a rendering bug. + // The targets differ only in how much text they carry beyond it. + let style = TokenStyle( + font: font, + iconTint: usageTint ?? foregroundColor, + iconTintIsUsageColor: usageTint != nil, + iconHeight: Self.iconHeight(size: options.size, isStacked: isStacked), + attributes: attributes, + usageAttributes: usageAttributes, + windowsLabeledByPercentToken: Self.windowsLabeledByPercentToken(in: layout)) let result = NSMutableAttributedString() var accessibilityLines: [String] = [] @@ -165,11 +226,7 @@ final class MenuBarLayoutRenderer { token, data: data, icon: icon, - style: TokenStyle( - font: font, - foregroundColor: foregroundColor, - iconHeight: Self.iconHeight(size: options.size, isStacked: isStacked), - attributes: attributes), + style: style, options: options) result.append(renderedItem.value) if let accessibilityText = renderedItem.accessibilityText { @@ -208,7 +265,10 @@ final class MenuBarLayoutRenderer { attributes: style.attributes) } let attachment = NSTextAttachment() - attachment.image = Self.attachmentImage(icon, tint: style.foregroundColor) + attachment.image = Self.attachmentImage( + icon, + tint: style.iconTint, + keepsTemplate: !style.iconTintIsUsageColor) let height = style.iconHeight let width = icon.size.height > 0 ? icon.size.width * height / icon.size.height : height attachment.bounds = NSRect( @@ -233,24 +293,17 @@ final class MenuBarLayoutRenderer { let rateWindow = Self.window(window, data: data) let percent = rateWindow.map { options.showUsed ? $0.usedPercent : $0.remainingPercent } let value = percent.map(UsageFormatter.percentString) ?? Self.missingValue - let prefix: String - let accessibilityPrefix: String - switch window { - case .session: - prefix = Self.sessionPrefix(rateWindow) - accessibilityPrefix = L("Session") - case .weekly: - prefix = "W" - accessibilityPrefix = L("Weekly") - case .automatic: - prefix = "" - accessibilityPrefix = L("Usage") + let prefix = Self.prefix(for: window) + let accessibilityPrefix: String = switch window { + case .session: L("Session") + case .weekly: L("Weekly") + case .automatic: L("Usage") } let display = prefix.isEmpty ? value : "\(prefix) \(value)" let accessibility = percent == nil ? L("%@ unavailable", accessibilityPrefix) : L("%@ %@", accessibilityPrefix, value) - return self.textToken(display, accessibilityText: accessibility, attributes: style.attributes) + return self.textToken(display, accessibilityText: accessibility, attributes: style.usageAttributes) case let .pace(window): return self.paceToken(window, data: data, style: style) case .usageBar: @@ -266,7 +319,7 @@ final class MenuBarLayoutRenderer { return self.textToken( value, accessibilityText: L("Usage bar, %d of 3 filled", filled), - attributes: style.attributes) + attributes: style.usageAttributes) case .resetCountdown: return self.resetToken( data.automatic?.resetsAt.map { UsageFormatter.resetCountdownDescription(from: $0, now: options.now) } @@ -283,7 +336,7 @@ final class MenuBarLayoutRenderer { return self.optionalTextToken( data.runsOut, unavailableLabel: L("Run-out estimate unavailable"), - attributes: style.attributes) + attributes: style.usageAttributes) case .costToday: return self.optionalTextToken( data.costToday, @@ -303,7 +356,7 @@ final class MenuBarLayoutRenderer { } } - private static func attachmentImage(_ image: NSImage, tint: NSColor) -> NSImage { + private static func attachmentImage(_ image: NSImage, tint: NSColor, keepsTemplate: Bool) -> NSImage { guard image.isTemplate else { return image } // NSTextAttachment draws an NSImage directly instead of through an image cell, so AppKit does not @@ -315,10 +368,24 @@ final class MenuBarLayoutRenderer { rect.fill(using: .sourceAtop) return true } - tintedImage.isTemplate = true + // A usage tint has to survive as-drawn: AppKit repaints template images in the label color, which + // would throw the color away. Baking it in is the same trade the meter icon makes. + tintedImage.isTemplate = keepsTemplate return tintedImage } + private static func windowsLabeledByPercentToken(in layout: MenuBarLayout) -> Set { + var windows: Set = [] + for line in layout.lines { + for token in line { + if case let .percent(window) = token { + windows.insert(window) + } + } + } + return windows + } + private static func resetToken( _ value: String?, unavailableLabel: String, @@ -355,7 +422,7 @@ final class MenuBarLayoutRenderer { (NSAttributedString(string: value, attributes: attributes), accessibilityText) } - private static func window( + fileprivate nonisolated static func window( _ percentWindow: PercentWindow, data: MenuBarLayoutRenderData) -> MenuBarLayoutRenderWindow? @@ -367,41 +434,35 @@ final class MenuBarLayoutRenderer { } } - /// Pace reuses the percent token's window vocabulary so a strip carrying both reads consistently. - /// Only the pace headline ("On pace", "23% in reserve") is shown; the run-out estimate that shares the - /// same pace calculation stays in the dedicated `.runsOut` token. + /// Pace reuses the percent token's window vocabulary so a strip carrying both reads consistently — but it + /// drops the prefix when the strip already labels that window, so `S 11% | S +20%` reads `S 11% | +20%`. + /// Only the compact pace delta is shown; the run-out estimate that shares the same pace calculation stays + /// in the dedicated `.runsOut` token, and the prose form stays in the dropdown. private static func paceToken( _ window: PercentWindow, data: MenuBarLayoutRenderData, style: TokenStyle) -> (value: NSAttributedString, accessibilityText: String?) { - let prefix: String - let accessibilityPrefix: String - switch window { - case .session: - prefix = Self.sessionPrefix(data.session) - accessibilityPrefix = L("Session pace") - case .weekly: - prefix = "W" - accessibilityPrefix = L("Weekly pace") - case .automatic: - prefix = "" - accessibilityPrefix = L("Pace") + let accessibilityPrefix: String = switch window { + case .session: L("Session pace") + case .weekly: L("Weekly pace") + case .automatic: L("Pace") } guard let paceValue = Self.pace(window, data: data) else { return self.textToken( self.missingValue, accessibilityText: L("%@ unavailable", accessibilityPrefix), - attributes: style.attributes) + attributes: style.usageAttributes) } + let prefix = style.windowsLabeledByPercentToken.contains(window) ? "" : Self.prefix(for: window) return self.textToken( prefix.isEmpty ? paceValue : "\(prefix) \(paceValue)", accessibilityText: L("%@ %@", accessibilityPrefix, paceValue), - attributes: style.attributes) + attributes: style.usageAttributes) } - private static func pace( + fileprivate nonisolated static func pace( _ percentWindow: PercentWindow, data: MenuBarLayoutRenderData) -> String? @@ -415,10 +476,14 @@ final class MenuBarLayoutRenderer { } } - private static func sessionPrefix(_ window: MenuBarLayoutRenderWindow?) -> String { - guard let minutes = window?.windowMinutes, minutes > 0 else { return "S" } - guard minutes.isMultiple(of: 60) else { return "\(minutes)m" } - return "\(minutes / 60)h" + /// Disambiguates two usage tokens in one strip. Deliberately a letter and not the window duration: `5h 11%` + /// reads as a countdown rather than as "11% of the 5-hour window", which is the opposite of what it means. + private static func prefix(for window: PercentWindow) -> String { + switch window { + case .session: "S" + case .weekly: "W" + case .automatic: "" + } } private static func fontSize(size: MenuBarLayoutSize, isStacked: Bool) -> CGFloat { diff --git a/Sources/CodexBar/MenuBarUsageTint.swift b/Sources/CodexBar/MenuBarUsageTint.swift index e46ca4f5ad..3c2dc910e3 100644 --- a/Sources/CodexBar/MenuBarUsageTint.swift +++ b/Sources/CodexBar/MenuBarUsageTint.swift @@ -11,6 +11,34 @@ import Foundation /// The values are mid-luminance and high-chroma so they stay legible against a light menu bar, a dark menu bar, /// and a translucent one over an arbitrary wallpaper. `NSColor.systemGreen` in particular is far too light on /// white. Color is never the only signal: the bar fill length already encodes the same value. +/// Which part of a menu bar layout strip `MenuBarUsageTint` colors. +/// +/// The meter icon is a single glyph, so the legacy render path has nothing to choose between. A layout strip is a +/// row of independent tokens, and which of them should carry the color is a taste call rather than a correct one. +enum MenuBarUsageColorTarget: String, CaseIterable, Identifiable, Sendable { + /// Each case adds to the one above it — the provider logo always carries the tint, because a colored + /// number beside an uncolored logo reads as a rendering bug rather than as a deliberate scope. + /// + /// Only the provider logo. + case icon + /// The logo plus percent, pace, usage bar, and run-out tokens — the ones whose value the color restates. + case usage + /// The whole strip, including account and cost tokens. + case everything + + var id: String { + self.rawValue + } + + var label: String { + switch self { + case .icon: L("menu_bar_usage_colors_target_icon") + case .usage: L("menu_bar_usage_colors_target_usage") + case .everything: L("menu_bar_usage_colors_target_everything") + } + } +} + enum MenuBarUsageTint { /// Comfortably inside the quota. private static let low = NSColor(srgbRed: 0.14, green: 0.60, blue: 0.25, alpha: 1) diff --git a/Sources/CodexBar/PreferencesMenuBarPane.swift b/Sources/CodexBar/PreferencesMenuBarPane.swift index cb90f958d1..c864ed7dcd 100644 --- a/Sources/CodexBar/PreferencesMenuBarPane.swift +++ b/Sources/CodexBar/PreferencesMenuBarPane.swift @@ -45,9 +45,23 @@ struct MenuBarPane: View { L("menu_bar_usage_colors_title"), subtitle: L("menu_bar_usage_colors_subtitle")) } - // The meter icon is what carries the tint; Icon + Percent renders the provider brand logo - // through MenuBarLayoutRenderer, which this setting does not touch. - .disabled(self.settings.menuBarIconStyle == .iconAndPercent) + + // Only the layout strip has more than one thing to tint. The meter icon is a single glyph, + // so the target picker would be a no-op choice on every other style. + SettingsMenuPicker( + selection: self.$settings.menuBarUsageColorTarget, + options: MenuBarUsageColorTarget.allCases, + label: { + SettingsRowLabel( + L("menu_bar_usage_colors_target_title"), + subtitle: L("menu_bar_usage_colors_target_subtitle")) + }, + optionLabel: { target in + Text(target.label) + }) + .disabled( + !self.settings.menuBarUsageColorsEnabled + || self.settings.menuBarIconStyle != .iconAndPercent) } header: { Text(L("section_icon")) } diff --git a/Sources/CodexBar/Resources/ar.lproj/Localizable.strings b/Sources/CodexBar/Resources/ar.lproj/Localizable.strings index 262b366fba..7b56d59f74 100644 --- a/Sources/CodexBar/Resources/ar.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ar.lproj/Localizable.strings @@ -567,6 +567,11 @@ "menu_bar_inactive_display_contrast_title" = "تحسين الوضوح على الشاشات غير النشطة"; "menu_bar_usage_colors_title" = "استخدام ملوّن"; "menu_bar_usage_colors_subtitle" = "تلوين أيقونة شريط القوائم من الأخضر إلى الأحمر مع ارتفاع الاستخدام."; +"menu_bar_usage_colors_target_title" = "يُطبَّق اللون على"; +"menu_bar_usage_colors_target_subtitle" = "«الأيقونة + النسبة» فقط. اختر أجزاء شريط القوائم التي تأخذ لون الاستخدام."; +"menu_bar_usage_colors_target_icon" = "الأيقونة"; +"menu_bar_usage_colors_target_usage" = "قيم الاستخدام"; +"menu_bar_usage_colors_target_everything" = "الشريط بأكمله"; "menu_bar_inactive_display_contrast_subtitle" = "استخدم عرضًا عالي التباين لإبقاء الأيقونة والمقياس قابلين للقراءة على الشاشات الأخرى."; "menu_bar_style_critters" = "الكائنات الصغيرة"; "menu_bar_style_bars" = "أشرطة القياس"; @@ -1331,9 +1336,9 @@ "menu_bar_layout_token_weekly" = "الأسبوعي %"; "menu_bar_layout_token_auto" = "نسبة تلقائية"; "menu_bar_layout_token_bar" = "شريط الاستخدام"; -"menu_bar_layout_token_session_pace" = "وتيرة الجلسة"; -"menu_bar_layout_token_weekly_pace" = "الوتيرة الأسبوعية"; -"menu_bar_layout_token_auto_pace" = "الوتيرة التلقائية"; +"menu_bar_layout_token_session_pace" = "الجلسة ±%"; +"menu_bar_layout_token_weekly_pace" = "الأسبوعي ±%"; +"menu_bar_layout_token_auto_pace" = "نسبة تلقائية ±%"; "Session pace" = "وتيرة الجلسة"; "Weekly pace" = "الوتيرة الأسبوعية"; "Pace" = "الوتيرة"; diff --git a/Sources/CodexBar/Resources/ca.lproj/Localizable.strings b/Sources/CodexBar/Resources/ca.lproj/Localizable.strings index 4e1b3a5ef7..15230de02e 100644 --- a/Sources/CodexBar/Resources/ca.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ca.lproj/Localizable.strings @@ -545,6 +545,11 @@ "menu_bar_inactive_display_contrast_title" = "Millora la visibilitat a les pantalles inactives"; "menu_bar_usage_colors_title" = "Ús amb codi de colors"; "menu_bar_usage_colors_subtitle" = "Acoloreix la icona de la barra de menús de verd a vermell a mesura que augmenta l'ús."; +"menu_bar_usage_colors_target_title" = "El color s’aplica a"; +"menu_bar_usage_colors_target_subtitle" = "Només Icona + percentatge. Tria quines parts de la barra de menús prenen el color d’ús."; +"menu_bar_usage_colors_target_icon" = "Icona"; +"menu_bar_usage_colors_target_usage" = "Valors d’ús"; +"menu_bar_usage_colors_target_everything" = "Tota la barra"; "menu_bar_inactive_display_contrast_subtitle" = "Utilitza una representació d'alt contrast perquè la icona i la mètrica siguin llegibles a les altres pantalles."; "menu_bar_style_critters" = "Bestioles"; "menu_bar_style_bars" = "Barres de mesura"; @@ -1330,9 +1335,9 @@ "menu_bar_layout_token_weekly" = "Setmanal %"; "menu_bar_layout_token_auto" = "% automàtic"; "menu_bar_layout_token_bar" = "Barra d’ús"; -"menu_bar_layout_token_session_pace" = "Ritme de sessió"; -"menu_bar_layout_token_weekly_pace" = "Ritme setmanal"; -"menu_bar_layout_token_auto_pace" = "Ritme automàtic"; +"menu_bar_layout_token_session_pace" = "Sessió ±%"; +"menu_bar_layout_token_weekly_pace" = "Setmanal ±%"; +"menu_bar_layout_token_auto_pace" = "±% automàtic"; "Session pace" = "Ritme de sessió"; "Weekly pace" = "Ritme setmanal"; "Pace" = "Ritme"; diff --git a/Sources/CodexBar/Resources/de.lproj/Localizable.strings b/Sources/CodexBar/Resources/de.lproj/Localizable.strings index 0002a7903d..79ffa1bcf4 100644 --- a/Sources/CodexBar/Resources/de.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/de.lproj/Localizable.strings @@ -559,6 +559,11 @@ "menu_bar_inactive_display_contrast_title" = "Sichtbarkeit auf inaktiven Displays verbessern"; "menu_bar_usage_colors_title" = "Farbcodierte Auslastung"; "menu_bar_usage_colors_subtitle" = "Färbt das Menüleistensymbol von Grün nach Rot, wenn die Auslastung steigt."; +"menu_bar_usage_colors_target_title" = "Farbe gilt für"; +"menu_bar_usage_colors_target_subtitle" = "Nur Symbol + Prozent. Wählen Sie, welche Teile der Menüleiste die Auslastungsfarbe annehmen."; +"menu_bar_usage_colors_target_icon" = "Symbol"; +"menu_bar_usage_colors_target_usage" = "Auslastungswerte"; +"menu_bar_usage_colors_target_everything" = "Gesamte Leiste"; "menu_bar_inactive_display_contrast_subtitle" = "Verwendet eine kontrastreiche Darstellung, damit Symbol und Messwert auf anderen Displays lesbar bleiben."; "menu_bar_style_critters" = "Kreaturen"; "menu_bar_style_bars" = "Messleisten"; @@ -1328,9 +1333,9 @@ "menu_bar_layout_token_weekly" = "Wöchentlich %"; "menu_bar_layout_token_auto" = "Automatisch %"; "menu_bar_layout_token_bar" = "Nutzungsleiste"; -"menu_bar_layout_token_session_pace" = "Sitzungstempo"; -"menu_bar_layout_token_weekly_pace" = "Wochentempo"; -"menu_bar_layout_token_auto_pace" = "Automatisches Tempo"; +"menu_bar_layout_token_session_pace" = "Sitzung ±%"; +"menu_bar_layout_token_weekly_pace" = "Wöchentlich ±%"; +"menu_bar_layout_token_auto_pace" = "Automatisch ±%"; "Session pace" = "Sitzungstempo"; "Weekly pace" = "Wochentempo"; "Pace" = "Tempo"; diff --git a/Sources/CodexBar/Resources/en.lproj/Localizable.strings b/Sources/CodexBar/Resources/en.lproj/Localizable.strings index b859b19f1c..7d0650ede2 100644 --- a/Sources/CodexBar/Resources/en.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/en.lproj/Localizable.strings @@ -567,6 +567,11 @@ "menu_bar_inactive_display_contrast_title" = "Improve visibility on inactive displays"; "menu_bar_usage_colors_title" = "Color-coded usage"; "menu_bar_usage_colors_subtitle" = "Tint the menu bar icon from green to red as usage rises."; +"menu_bar_usage_colors_target_title" = "Color applies to"; +"menu_bar_usage_colors_target_subtitle" = "Icon + Percent only. Choose which parts of the menu bar strip take the usage color."; +"menu_bar_usage_colors_target_icon" = "Icon"; +"menu_bar_usage_colors_target_usage" = "Usage values"; +"menu_bar_usage_colors_target_everything" = "Whole strip"; "menu_bar_inactive_display_contrast_subtitle" = "Use high-contrast rendering to keep the icon and metric readable on other displays."; "menu_bar_style_critters" = "Critters"; "menu_bar_style_bars" = "Meter bars"; @@ -1332,9 +1337,9 @@ "menu_bar_layout_token_weekly" = "Weekly %"; "menu_bar_layout_token_auto" = "Auto %"; "menu_bar_layout_token_bar" = "Usage bar"; -"menu_bar_layout_token_session_pace" = "Session pace"; -"menu_bar_layout_token_weekly_pace" = "Weekly pace"; -"menu_bar_layout_token_auto_pace" = "Auto pace"; +"menu_bar_layout_token_session_pace" = "Session ±%"; +"menu_bar_layout_token_weekly_pace" = "Weekly ±%"; +"menu_bar_layout_token_auto_pace" = "Auto ±%"; "Session pace" = "Session pace"; "Weekly pace" = "Weekly pace"; "Pace" = "Pace"; diff --git a/Sources/CodexBar/Resources/es.lproj/Localizable.strings b/Sources/CodexBar/Resources/es.lproj/Localizable.strings index ae3695dc84..c398271da1 100644 --- a/Sources/CodexBar/Resources/es.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/es.lproj/Localizable.strings @@ -553,6 +553,11 @@ "menu_bar_inactive_display_contrast_title" = "Mejorar la visibilidad en pantallas inactivas"; "menu_bar_usage_colors_title" = "Uso codificado por colores"; "menu_bar_usage_colors_subtitle" = "Colorea el icono de la barra de menús de verde a rojo a medida que aumenta el uso."; +"menu_bar_usage_colors_target_title" = "El color se aplica a"; +"menu_bar_usage_colors_target_subtitle" = "Solo Icono + porcentaje. Elige qué partes de la barra de menús toman el color de uso."; +"menu_bar_usage_colors_target_icon" = "Icono"; +"menu_bar_usage_colors_target_usage" = "Valores de uso"; +"menu_bar_usage_colors_target_everything" = "Toda la barra"; "menu_bar_inactive_display_contrast_subtitle" = "Usa un renderizado de alto contraste para mantener legibles el icono y la métrica en otras pantallas."; "menu_bar_style_critters" = "Bichitos"; "menu_bar_style_bars" = "Barras de medición"; @@ -1326,9 +1331,9 @@ "menu_bar_layout_token_weekly" = "Semanal %"; "menu_bar_layout_token_auto" = "% automático"; "menu_bar_layout_token_bar" = "Barra de uso"; -"menu_bar_layout_token_session_pace" = "Ritmo de sesión"; -"menu_bar_layout_token_weekly_pace" = "Ritmo semanal"; -"menu_bar_layout_token_auto_pace" = "Ritmo automático"; +"menu_bar_layout_token_session_pace" = "Sesión ±%"; +"menu_bar_layout_token_weekly_pace" = "Semanal ±%"; +"menu_bar_layout_token_auto_pace" = "±% automático"; "Session pace" = "Ritmo de sesión"; "Weekly pace" = "Ritmo semanal"; "Pace" = "Ritmo"; diff --git a/Sources/CodexBar/Resources/fa.lproj/Localizable.strings b/Sources/CodexBar/Resources/fa.lproj/Localizable.strings index 705e2321bb..d37150b9aa 100644 --- a/Sources/CodexBar/Resources/fa.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/fa.lproj/Localizable.strings @@ -567,6 +567,11 @@ "menu_bar_inactive_display_contrast_title" = "بهبود خوانایی در نمایشگرهای غیرفعال"; "menu_bar_usage_colors_title" = "مصرف رنگی"; "menu_bar_usage_colors_subtitle" = "نماد نوار منو را با افزایش مصرف از سبز به قرمز رنگ می‌کند."; +"menu_bar_usage_colors_target_title" = "رنگ اعمال می‌شود بر"; +"menu_bar_usage_colors_target_subtitle" = "فقط «نماد + درصد». انتخاب کنید کدام بخش‌های نوار منو رنگ مصرف را بگیرند."; +"menu_bar_usage_colors_target_icon" = "نماد"; +"menu_bar_usage_colors_target_usage" = "مقادیر مصرف"; +"menu_bar_usage_colors_target_everything" = "کل نوار"; "menu_bar_inactive_display_contrast_subtitle" = "از نمایش با کنتراست بالا استفاده می‌کند تا نماد و معیار در نمایشگرهای دیگر خوانا بمانند."; "menu_bar_style_critters" = "موجودات"; "menu_bar_style_bars" = "نوارهای اندازه‌گیری"; @@ -1331,9 +1336,9 @@ "menu_bar_layout_token_weekly" = "هفتگی %"; "menu_bar_layout_token_auto" = "درصد خودکار"; "menu_bar_layout_token_bar" = "نوار مصرف"; -"menu_bar_layout_token_session_pace" = "سرعت جلسه"; -"menu_bar_layout_token_weekly_pace" = "سرعت هفتگی"; -"menu_bar_layout_token_auto_pace" = "سرعت خودکار"; +"menu_bar_layout_token_session_pace" = "جلسه ±%"; +"menu_bar_layout_token_weekly_pace" = "هفتگی ±%"; +"menu_bar_layout_token_auto_pace" = "درصد خودکار ±%"; "Session pace" = "سرعت جلسه"; "Weekly pace" = "سرعت هفتگی"; "Pace" = "سرعت"; diff --git a/Sources/CodexBar/Resources/fr.lproj/Localizable.strings b/Sources/CodexBar/Resources/fr.lproj/Localizable.strings index b52fe44569..edf3719db4 100644 --- a/Sources/CodexBar/Resources/fr.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/fr.lproj/Localizable.strings @@ -561,6 +561,11 @@ "menu_bar_inactive_display_contrast_title" = "Améliorer la visibilité sur les écrans inactifs"; "menu_bar_usage_colors_title" = "Utilisation en couleurs"; "menu_bar_usage_colors_subtitle" = "Colore l'icône de la barre des menus du vert au rouge à mesure que l'utilisation augmente."; +"menu_bar_usage_colors_target_title" = "La couleur s’applique à"; +"menu_bar_usage_colors_target_subtitle" = "Icône + pourcentage uniquement. Choisissez les parties de la barre des menus qui prennent la couleur d’utilisation."; +"menu_bar_usage_colors_target_icon" = "Icône"; +"menu_bar_usage_colors_target_usage" = "Valeurs d’utilisation"; +"menu_bar_usage_colors_target_everything" = "Toute la barre"; "menu_bar_inactive_display_contrast_subtitle" = "Utilise un rendu à contraste élevé pour que l’icône et la mesure restent lisibles sur les autres écrans."; "menu_bar_style_critters" = "Créatures"; "menu_bar_style_bars" = "Barres de mesure"; @@ -1327,9 +1332,9 @@ "menu_bar_layout_token_weekly" = "Hebdomadaire %"; "menu_bar_layout_token_auto" = "% auto"; "menu_bar_layout_token_bar" = "Barre d’utilisation"; -"menu_bar_layout_token_session_pace" = "Rythme de session"; -"menu_bar_layout_token_weekly_pace" = "Rythme hebdomadaire"; -"menu_bar_layout_token_auto_pace" = "Rythme auto"; +"menu_bar_layout_token_session_pace" = "Session ±%"; +"menu_bar_layout_token_weekly_pace" = "Hebdomadaire ±%"; +"menu_bar_layout_token_auto_pace" = "±% auto"; "Session pace" = "Rythme de session"; "Weekly pace" = "Rythme hebdomadaire"; "Pace" = "Rythme"; diff --git a/Sources/CodexBar/Resources/gl.lproj/Localizable.strings b/Sources/CodexBar/Resources/gl.lproj/Localizable.strings index deec0765fa..be9f8d1075 100644 --- a/Sources/CodexBar/Resources/gl.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/gl.lproj/Localizable.strings @@ -540,6 +540,11 @@ "menu_bar_inactive_display_contrast_title" = "Mellorar a visibilidade nas pantallas inactivas"; "menu_bar_usage_colors_title" = "Uso con código de cores"; "menu_bar_usage_colors_subtitle" = "Colorea a icona da barra de menús de verde a vermello a medida que aumenta o uso."; +"menu_bar_usage_colors_target_title" = "A cor aplícase a"; +"menu_bar_usage_colors_target_subtitle" = "Só Icona + porcentaxe. Escolle que partes da barra de menús toman a cor de uso."; +"menu_bar_usage_colors_target_icon" = "Icona"; +"menu_bar_usage_colors_target_usage" = "Valores de uso"; +"menu_bar_usage_colors_target_everything" = "Toda a barra"; "menu_bar_inactive_display_contrast_subtitle" = "Usa unha representación de alto contraste para manter lexibles a icona e a métrica nas outras pantallas."; "menu_bar_style_critters" = "Animaliños"; "menu_bar_style_bars" = "Barras de medición"; @@ -1327,9 +1332,9 @@ "menu_bar_layout_token_weekly" = "Semanal %"; "menu_bar_layout_token_auto" = "% automática"; "menu_bar_layout_token_bar" = "Barra de uso"; -"menu_bar_layout_token_session_pace" = "Ritmo de sesión"; -"menu_bar_layout_token_weekly_pace" = "Ritmo semanal"; -"menu_bar_layout_token_auto_pace" = "Ritmo automático"; +"menu_bar_layout_token_session_pace" = "Sesión ±%"; +"menu_bar_layout_token_weekly_pace" = "Semanal ±%"; +"menu_bar_layout_token_auto_pace" = "±% automática"; "Session pace" = "Ritmo de sesión"; "Weekly pace" = "Ritmo semanal"; "Pace" = "Ritmo"; diff --git a/Sources/CodexBar/Resources/id.lproj/Localizable.strings b/Sources/CodexBar/Resources/id.lproj/Localizable.strings index c244cc6e18..c8087d267c 100644 --- a/Sources/CodexBar/Resources/id.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/id.lproj/Localizable.strings @@ -569,6 +569,11 @@ "menu_bar_inactive_display_contrast_title" = "Tingkatkan visibilitas di layar tidak aktif"; "menu_bar_usage_colors_title" = "Penggunaan berwarna"; "menu_bar_usage_colors_subtitle" = "Mewarnai ikon bilah menu dari hijau ke merah seiring meningkatnya penggunaan."; +"menu_bar_usage_colors_target_title" = "Warna berlaku untuk"; +"menu_bar_usage_colors_target_subtitle" = "Hanya Ikon + persen. Pilih bagian bilah menu mana yang memakai warna penggunaan."; +"menu_bar_usage_colors_target_icon" = "Ikon"; +"menu_bar_usage_colors_target_usage" = "Nilai penggunaan"; +"menu_bar_usage_colors_target_everything" = "Seluruh bilah"; "menu_bar_inactive_display_contrast_subtitle" = "Gunakan rendering kontras tinggi agar ikon dan metrik tetap terbaca di layar lain."; "menu_bar_style_critters" = "Critter"; "menu_bar_style_bars" = "Bilah meter"; @@ -1331,9 +1336,9 @@ "menu_bar_layout_token_weekly" = "Mingguan %"; "menu_bar_layout_token_auto" = "% otomatis"; "menu_bar_layout_token_bar" = "Bar penggunaan"; -"menu_bar_layout_token_session_pace" = "Pace sesi"; -"menu_bar_layout_token_weekly_pace" = "Pace mingguan"; -"menu_bar_layout_token_auto_pace" = "Pace otomatis"; +"menu_bar_layout_token_session_pace" = "Sesi ±%"; +"menu_bar_layout_token_weekly_pace" = "Mingguan ±%"; +"menu_bar_layout_token_auto_pace" = "±% otomatis"; "Session pace" = "Pace sesi"; "Weekly pace" = "Pace mingguan"; "Pace" = "Pace"; diff --git a/Sources/CodexBar/Resources/it.lproj/Localizable.strings b/Sources/CodexBar/Resources/it.lproj/Localizable.strings index e7ceeca649..2ac3afd486 100644 --- a/Sources/CodexBar/Resources/it.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/it.lproj/Localizable.strings @@ -569,6 +569,11 @@ "menu_bar_inactive_display_contrast_title" = "Migliora la visibilità sugli schermi inattivi"; "menu_bar_usage_colors_title" = "Utilizzo a colori"; "menu_bar_usage_colors_subtitle" = "Colora l'icona nella barra dei menu dal verde al rosso man mano che l'utilizzo aumenta."; +"menu_bar_usage_colors_target_title" = "Il colore si applica a"; +"menu_bar_usage_colors_target_subtitle" = "Solo Icona + percentuale. Scegli quali parti della barra dei menu assumono il colore di utilizzo."; +"menu_bar_usage_colors_target_icon" = "Icona"; +"menu_bar_usage_colors_target_usage" = "Valori di utilizzo"; +"menu_bar_usage_colors_target_everything" = "Intera barra"; "menu_bar_inactive_display_contrast_subtitle" = "Usa un rendering ad alto contrasto per mantenere leggibili icona e metrica sugli altri schermi."; "menu_bar_style_critters" = "Creature"; "menu_bar_style_bars" = "Barre di livello"; @@ -1331,9 +1336,9 @@ "menu_bar_layout_token_weekly" = "Settimanale %"; "menu_bar_layout_token_auto" = "% automatica"; "menu_bar_layout_token_bar" = "Barra utilizzo"; -"menu_bar_layout_token_session_pace" = "Andamento sessione"; -"menu_bar_layout_token_weekly_pace" = "Andamento settimanale"; -"menu_bar_layout_token_auto_pace" = "Andamento automatico"; +"menu_bar_layout_token_session_pace" = "Sessione ±%"; +"menu_bar_layout_token_weekly_pace" = "Settimanale ±%"; +"menu_bar_layout_token_auto_pace" = "±% automatica"; "Session pace" = "Andamento sessione"; "Weekly pace" = "Andamento settimanale"; "Pace" = "Andamento"; diff --git a/Sources/CodexBar/Resources/ja.lproj/Localizable.strings b/Sources/CodexBar/Resources/ja.lproj/Localizable.strings index 9ed8832850..77050a4db0 100644 --- a/Sources/CodexBar/Resources/ja.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ja.lproj/Localizable.strings @@ -558,6 +558,11 @@ "menu_bar_inactive_display_contrast_title" = "非アクティブなディスプレイでの視認性を向上"; "menu_bar_usage_colors_title" = "使用量の色分け"; "menu_bar_usage_colors_subtitle" = "使用量が増えるとメニューバーアイコンを緑から赤へ色付けします。"; +"menu_bar_usage_colors_target_title" = "色を適用する対象"; +"menu_bar_usage_colors_target_subtitle" = "「アイコン + パーセント」のみ。メニューバーのどの部分に使用量の色を適用するかを選びます。"; +"menu_bar_usage_colors_target_icon" = "アイコン"; +"menu_bar_usage_colors_target_usage" = "使用量の値"; +"menu_bar_usage_colors_target_everything" = "全体"; "menu_bar_inactive_display_contrast_subtitle" = "高コントラスト表示を使用し、ほかのディスプレイでもアイコンと指標を読みやすくします。"; "menu_bar_style_critters" = "クリッター"; "menu_bar_style_bars" = "メーターバー"; @@ -1328,9 +1333,9 @@ "menu_bar_layout_token_weekly" = "週間 %"; "menu_bar_layout_token_auto" = "自動 %"; "menu_bar_layout_token_bar" = "使用量バー"; -"menu_bar_layout_token_session_pace" = "セッションペース"; -"menu_bar_layout_token_weekly_pace" = "週間ペース"; -"menu_bar_layout_token_auto_pace" = "自動ペース"; +"menu_bar_layout_token_session_pace" = "セッション ±%"; +"menu_bar_layout_token_weekly_pace" = "週間 ±%"; +"menu_bar_layout_token_auto_pace" = "自動 ±%"; "Session pace" = "セッションペース"; "Weekly pace" = "週間ペース"; "Pace" = "ペース"; diff --git a/Sources/CodexBar/Resources/ko.lproj/Localizable.strings b/Sources/CodexBar/Resources/ko.lproj/Localizable.strings index 898274152c..b26ef3ca39 100644 --- a/Sources/CodexBar/Resources/ko.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ko.lproj/Localizable.strings @@ -550,6 +550,11 @@ "menu_bar_inactive_display_contrast_title" = "비활성 디스플레이에서 가시성 향상"; "menu_bar_usage_colors_title" = "사용량 색상 표시"; "menu_bar_usage_colors_subtitle" = "사용량이 늘어남에 따라 메뉴 막대 아이콘을 초록색에서 빨간색으로 표시합니다."; +"menu_bar_usage_colors_target_title" = "색상 적용 대상"; +"menu_bar_usage_colors_target_subtitle" = "‘아이콘 + 퍼센트’ 전용. 메뉴 막대에서 사용량 색상을 적용할 부분을 선택합니다."; +"menu_bar_usage_colors_target_icon" = "아이콘"; +"menu_bar_usage_colors_target_usage" = "사용량 값"; +"menu_bar_usage_colors_target_everything" = "전체"; "menu_bar_inactive_display_contrast_subtitle" = "고대비 렌더링을 사용하여 다른 디스플레이에서도 아이콘과 지표를 읽기 쉽게 유지합니다."; "menu_bar_style_critters" = "크리터"; "menu_bar_style_bars" = "미터 막대"; @@ -1295,9 +1300,9 @@ "menu_bar_layout_token_weekly" = "주간 %"; "menu_bar_layout_token_auto" = "자동 %"; "menu_bar_layout_token_bar" = "사용량 막대"; -"menu_bar_layout_token_session_pace" = "세션 사용 속도"; -"menu_bar_layout_token_weekly_pace" = "주간 사용 속도"; -"menu_bar_layout_token_auto_pace" = "자동 사용 속도"; +"menu_bar_layout_token_session_pace" = "세션 ±%"; +"menu_bar_layout_token_weekly_pace" = "주간 ±%"; +"menu_bar_layout_token_auto_pace" = "자동 ±%"; "Session pace" = "세션 사용 속도"; "Weekly pace" = "주간 사용 속도"; "Pace" = "사용 속도"; diff --git a/Sources/CodexBar/Resources/nl.lproj/Localizable.strings b/Sources/CodexBar/Resources/nl.lproj/Localizable.strings index 33f6557078..c0e4ad8300 100644 --- a/Sources/CodexBar/Resources/nl.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/nl.lproj/Localizable.strings @@ -561,6 +561,11 @@ "menu_bar_inactive_display_contrast_title" = "Zichtbaarheid op inactieve beeldschermen verbeteren"; "menu_bar_usage_colors_title" = "Gebruik met kleurcodering"; "menu_bar_usage_colors_subtitle" = "Kleurt het menubalksymbool van groen naar rood naarmate het gebruik stijgt."; +"menu_bar_usage_colors_target_title" = "Kleur geldt voor"; +"menu_bar_usage_colors_target_subtitle" = "Alleen Pictogram + percentage. Kies welke delen van de menubalk de gebruikskleur krijgen."; +"menu_bar_usage_colors_target_icon" = "Pictogram"; +"menu_bar_usage_colors_target_usage" = "Gebruikswaarden"; +"menu_bar_usage_colors_target_everything" = "Hele balk"; "menu_bar_inactive_display_contrast_subtitle" = "Gebruikt weergave met hoog contrast zodat het pictogram en de metriek leesbaar blijven op andere beeldschermen."; "menu_bar_style_critters" = "Critters"; "menu_bar_style_bars" = "Meterbalken"; @@ -1327,9 +1332,9 @@ "menu_bar_layout_token_weekly" = "Wekelijks %"; "menu_bar_layout_token_auto" = "Automatisch %"; "menu_bar_layout_token_bar" = "Gebruiksbalk"; -"menu_bar_layout_token_session_pace" = "Sessietempo"; -"menu_bar_layout_token_weekly_pace" = "Wekelijks tempo"; -"menu_bar_layout_token_auto_pace" = "Automatisch tempo"; +"menu_bar_layout_token_session_pace" = "Sessie ±%"; +"menu_bar_layout_token_weekly_pace" = "Wekelijks ±%"; +"menu_bar_layout_token_auto_pace" = "Automatisch ±%"; "Session pace" = "Sessietempo"; "Weekly pace" = "Wekelijks tempo"; "Pace" = "Tempo"; diff --git a/Sources/CodexBar/Resources/pl.lproj/Localizable.strings b/Sources/CodexBar/Resources/pl.lproj/Localizable.strings index cd1102e4ba..04f6d1f9b8 100644 --- a/Sources/CodexBar/Resources/pl.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/pl.lproj/Localizable.strings @@ -569,6 +569,11 @@ "menu_bar_inactive_display_contrast_title" = "Popraw widoczność na nieaktywnych ekranach"; "menu_bar_usage_colors_title" = "Zużycie oznaczone kolorami"; "menu_bar_usage_colors_subtitle" = "Zabarwia ikonę paska menu od zieleni do czerwieni wraz ze wzrostem zużycia."; +"menu_bar_usage_colors_target_title" = "Kolor dotyczy"; +"menu_bar_usage_colors_target_subtitle" = "Tylko Ikona + procent. Wybierz, które części paska menu przyjmują kolor zużycia."; +"menu_bar_usage_colors_target_icon" = "Ikona"; +"menu_bar_usage_colors_target_usage" = "Wartości zużycia"; +"menu_bar_usage_colors_target_everything" = "Cały pasek"; "menu_bar_inactive_display_contrast_subtitle" = "Używa renderowania o wysokim kontraście, aby ikona i wskaźnik pozostały czytelne na innych ekranach."; "menu_bar_style_critters" = "Stworki"; "menu_bar_style_bars" = "Paski miernika"; @@ -1331,9 +1336,9 @@ "menu_bar_layout_token_weekly" = "Tydzień %"; "menu_bar_layout_token_auto" = "Automatycznie %"; "menu_bar_layout_token_bar" = "Pasek użycia"; -"menu_bar_layout_token_session_pace" = "Tempo sesji"; -"menu_bar_layout_token_weekly_pace" = "Tempo tygodniowe"; -"menu_bar_layout_token_auto_pace" = "Tempo automatyczne"; +"menu_bar_layout_token_session_pace" = "Sesja ±%"; +"menu_bar_layout_token_weekly_pace" = "Tydzień ±%"; +"menu_bar_layout_token_auto_pace" = "Automatycznie ±%"; "Session pace" = "Tempo sesji"; "Weekly pace" = "Tempo tygodniowe"; "Pace" = "Tempo"; diff --git a/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings b/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings index 6cd468d50e..bc11f666c7 100644 --- a/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings @@ -558,6 +558,11 @@ "menu_bar_inactive_display_contrast_title" = "Melhorar a visibilidade em telas inativas"; "menu_bar_usage_colors_title" = "Uso codificado por cores"; "menu_bar_usage_colors_subtitle" = "Colore o ícone da barra de menus de verde a vermelho conforme o uso aumenta."; +"menu_bar_usage_colors_target_title" = "A cor se aplica a"; +"menu_bar_usage_colors_target_subtitle" = "Somente Ícone + porcentagem. Escolha quais partes da barra de menus recebem a cor de uso."; +"menu_bar_usage_colors_target_icon" = "Ícone"; +"menu_bar_usage_colors_target_usage" = "Valores de uso"; +"menu_bar_usage_colors_target_everything" = "Barra inteira"; "menu_bar_inactive_display_contrast_subtitle" = "Usa renderização de alto contraste para manter o ícone e a métrica legíveis em outras telas."; "menu_bar_style_critters" = "Bichinhos"; "menu_bar_style_bars" = "Barras de medição"; @@ -1328,9 +1333,9 @@ "menu_bar_layout_token_weekly" = "Semanal %"; "menu_bar_layout_token_auto" = "% automático"; "menu_bar_layout_token_bar" = "Barra de uso"; -"menu_bar_layout_token_session_pace" = "Ritmo da sessão"; -"menu_bar_layout_token_weekly_pace" = "Ritmo semanal"; -"menu_bar_layout_token_auto_pace" = "Ritmo automático"; +"menu_bar_layout_token_session_pace" = "Sessão ±%"; +"menu_bar_layout_token_weekly_pace" = "Semanal ±%"; +"menu_bar_layout_token_auto_pace" = "±% automático"; "Session pace" = "Ritmo da sessão"; "Weekly pace" = "Ritmo semanal"; "Pace" = "Ritmo"; diff --git a/Sources/CodexBar/Resources/ru.lproj/Localizable.strings b/Sources/CodexBar/Resources/ru.lproj/Localizable.strings index 92912495c5..e688fb1fea 100644 --- a/Sources/CodexBar/Resources/ru.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ru.lproj/Localizable.strings @@ -562,6 +562,11 @@ "menu_bar_inactive_display_contrast_title" = "Повысить видимость на неактивных дисплеях"; "menu_bar_usage_colors_title" = "Цветовая индикация расхода"; "menu_bar_usage_colors_subtitle" = "Окрашивает значок в строке меню от зелёного к красному по мере роста расхода."; +"menu_bar_usage_colors_target_title" = "Цвет применяется к"; +"menu_bar_usage_colors_target_subtitle" = "Только «Значок + процент». Выберите, какие части строки меню принимают цвет использования."; +"menu_bar_usage_colors_target_icon" = "Значок"; +"menu_bar_usage_colors_target_usage" = "Значения использования"; +"menu_bar_usage_colors_target_everything" = "Вся строка"; "menu_bar_inactive_display_contrast_subtitle" = "Использует высококонтрастную отрисовку, чтобы значок и показатель оставались читаемыми на других дисплеях."; "menu_bar_style_critters" = "Декоративные индикаторы"; "menu_bar_style_bars" = "Полосы-индикаторы"; @@ -1329,9 +1334,9 @@ "menu_bar_layout_token_weekly" = "Недельный %"; "menu_bar_layout_token_auto" = "Авто %"; "menu_bar_layout_token_bar" = "Индикатор использования"; -"menu_bar_layout_token_session_pace" = "Темп сеанса"; -"menu_bar_layout_token_weekly_pace" = "Недельный темп"; -"menu_bar_layout_token_auto_pace" = "Автоматический темп"; +"menu_bar_layout_token_session_pace" = "Сеанс ±%"; +"menu_bar_layout_token_weekly_pace" = "Недельный ±%"; +"menu_bar_layout_token_auto_pace" = "Авто ±%"; "Session pace" = "Темп сеанса"; "Weekly pace" = "Недельный темп"; "Pace" = "Темп"; diff --git a/Sources/CodexBar/Resources/sv.lproj/Localizable.strings b/Sources/CodexBar/Resources/sv.lproj/Localizable.strings index 34b009f8b8..5d9ae68277 100644 --- a/Sources/CodexBar/Resources/sv.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/sv.lproj/Localizable.strings @@ -560,6 +560,11 @@ "menu_bar_inactive_display_contrast_title" = "Förbättra synligheten på inaktiva skärmar"; "menu_bar_usage_colors_title" = "Färgkodad användning"; "menu_bar_usage_colors_subtitle" = "Färgar menyradsikonen från grönt till rött när användningen ökar."; +"menu_bar_usage_colors_target_title" = "Färgen gäller"; +"menu_bar_usage_colors_target_subtitle" = "Endast Ikon + procent. Välj vilka delar av menyraden som får användningsfärgen."; +"menu_bar_usage_colors_target_icon" = "Ikon"; +"menu_bar_usage_colors_target_usage" = "Användningsvärden"; +"menu_bar_usage_colors_target_everything" = "Hela raden"; "menu_bar_inactive_display_contrast_subtitle" = "Använder kontrastrik rendering så att ikonen och mätvärdet förblir läsbara på andra skärmar."; "menu_bar_style_critters" = "Figurer"; "menu_bar_style_bars" = "Mätarstaplar"; @@ -1326,9 +1331,9 @@ "menu_bar_layout_token_weekly" = "Vecka %"; "menu_bar_layout_token_auto" = "Automatiskt %"; "menu_bar_layout_token_bar" = "Användningsstapel"; -"menu_bar_layout_token_session_pace" = "Sessionstakt"; -"menu_bar_layout_token_weekly_pace" = "Veckotakt"; -"menu_bar_layout_token_auto_pace" = "Automatisk takt"; +"menu_bar_layout_token_session_pace" = "Session ±%"; +"menu_bar_layout_token_weekly_pace" = "Vecka ±%"; +"menu_bar_layout_token_auto_pace" = "Automatiskt ±%"; "Session pace" = "Sessionstakt"; "Weekly pace" = "Veckotakt"; "Pace" = "Takt"; diff --git a/Sources/CodexBar/Resources/th.lproj/Localizable.strings b/Sources/CodexBar/Resources/th.lproj/Localizable.strings index b1ca7a8ecc..e9e6bad47e 100644 --- a/Sources/CodexBar/Resources/th.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/th.lproj/Localizable.strings @@ -567,6 +567,11 @@ "menu_bar_inactive_display_contrast_title" = "เพิ่มการมองเห็นบนจอแสดงผลที่ไม่ได้ใช้งาน"; "menu_bar_usage_colors_title" = "แสดงการใช้งานด้วยสี"; "menu_bar_usage_colors_subtitle" = "ไล่สีไอคอนบนแถบเมนูจากเขียวไปแดงเมื่อการใช้งานเพิ่มขึ้น"; +"menu_bar_usage_colors_target_title" = "ใช้สีกับ"; +"menu_bar_usage_colors_target_subtitle" = "เฉพาะ “ไอคอน + เปอร์เซ็นต์” เลือกว่าส่วนใดของแถบเมนูจะใช้สีตามการใช้งาน"; +"menu_bar_usage_colors_target_icon" = "ไอคอน"; +"menu_bar_usage_colors_target_usage" = "ค่าการใช้งาน"; +"menu_bar_usage_colors_target_everything" = "ทั้งแถบ"; "menu_bar_inactive_display_contrast_subtitle" = "ใช้การแสดงผลแบบคอนทราสต์สูงเพื่อให้ไอคอนและค่าชี้วัดอ่านได้บนจออื่น"; "menu_bar_style_critters" = "ตัวการ์ตูน"; "menu_bar_style_bars" = "แถบวัด"; @@ -1331,9 +1336,9 @@ "menu_bar_layout_token_weekly" = "รายสัปดาห์ %"; "menu_bar_layout_token_auto" = "% อัตโนมัติ"; "menu_bar_layout_token_bar" = "แถบการใช้งาน"; -"menu_bar_layout_token_session_pace" = "อัตราการใช้เซสชัน"; -"menu_bar_layout_token_weekly_pace" = "อัตราการใช้รายสัปดาห์"; -"menu_bar_layout_token_auto_pace" = "อัตราการใช้อัตโนมัติ"; +"menu_bar_layout_token_session_pace" = "เซสชั่น ±%"; +"menu_bar_layout_token_weekly_pace" = "รายสัปดาห์ ±%"; +"menu_bar_layout_token_auto_pace" = "±% อัตโนมัติ"; "Session pace" = "อัตราการใช้เซสชัน"; "Weekly pace" = "อัตราการใช้รายสัปดาห์"; "Pace" = "อัตราการใช้"; diff --git a/Sources/CodexBar/Resources/tr.lproj/Localizable.strings b/Sources/CodexBar/Resources/tr.lproj/Localizable.strings index ba8d2e6363..d77fc3f823 100644 --- a/Sources/CodexBar/Resources/tr.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/tr.lproj/Localizable.strings @@ -567,6 +567,11 @@ "menu_bar_inactive_display_contrast_title" = "Etkin olmayan ekranlarda görünürlüğü artır"; "menu_bar_usage_colors_title" = "Renk kodlu kullanım"; "menu_bar_usage_colors_subtitle" = "Kullanım arttıkça menü çubuğu simgesini yeşilden kırmızıya renklendirir."; +"menu_bar_usage_colors_target_title" = "Renk şuna uygulanır"; +"menu_bar_usage_colors_target_subtitle" = "Yalnızca Simge + yüzde. Menü çubuğunun hangi bölümlerinin kullanım rengini alacağını seçin."; +"menu_bar_usage_colors_target_icon" = "Simge"; +"menu_bar_usage_colors_target_usage" = "Kullanım değerleri"; +"menu_bar_usage_colors_target_everything" = "Tüm çubuk"; "menu_bar_inactive_display_contrast_subtitle" = "Simge ve ölçümün diğer ekranlarda okunabilir kalması için yüksek kontrastlı işleme kullanır."; "menu_bar_style_critters" = "Canavarlar"; "menu_bar_style_bars" = "Ölçüm çubukları"; @@ -1329,9 +1334,9 @@ "menu_bar_layout_token_weekly" = "Haftalık %"; "menu_bar_layout_token_auto" = "Otomatik %"; "menu_bar_layout_token_bar" = "Kullanım çubuğu"; -"menu_bar_layout_token_session_pace" = "Oturum hızı"; -"menu_bar_layout_token_weekly_pace" = "Haftalık hız"; -"menu_bar_layout_token_auto_pace" = "Otomatik hız"; +"menu_bar_layout_token_session_pace" = "Oturum ±%"; +"menu_bar_layout_token_weekly_pace" = "Haftalık ±%"; +"menu_bar_layout_token_auto_pace" = "Otomatik ±%"; "Session pace" = "Oturum hızı"; "Weekly pace" = "Haftalık hız"; "Pace" = "Hız"; diff --git a/Sources/CodexBar/Resources/uk.lproj/Localizable.strings b/Sources/CodexBar/Resources/uk.lproj/Localizable.strings index d2a92a970b..a259fd9dab 100644 --- a/Sources/CodexBar/Resources/uk.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/uk.lproj/Localizable.strings @@ -561,6 +561,11 @@ "menu_bar_inactive_display_contrast_title" = "Покращити видимість на неактивних дисплеях"; "menu_bar_usage_colors_title" = "Кольорова індикація витрат"; "menu_bar_usage_colors_subtitle" = "Забарвлює піктограму в рядку меню від зеленого до червоного зі зростанням витрат."; +"menu_bar_usage_colors_target_title" = "Колір застосовується до"; +"menu_bar_usage_colors_target_subtitle" = "Лише «Піктограма + відсоток». Виберіть, які частини рядка меню отримують колір використання."; +"menu_bar_usage_colors_target_icon" = "Піктограма"; +"menu_bar_usage_colors_target_usage" = "Значення використання"; +"menu_bar_usage_colors_target_everything" = "Увесь рядок"; "menu_bar_inactive_display_contrast_subtitle" = "Використовує висококонтрастне відтворення, щоб піктограма й показник залишалися читабельними на інших дисплеях."; "menu_bar_style_critters" = "Тварини"; "menu_bar_style_bars" = "Смуги-індикатори"; @@ -1327,9 +1332,9 @@ "menu_bar_layout_token_weekly" = "Щотижня %"; "menu_bar_layout_token_auto" = "Авто %"; "menu_bar_layout_token_bar" = "Індикатор використання"; -"menu_bar_layout_token_session_pace" = "Темп сесії"; -"menu_bar_layout_token_weekly_pace" = "Тижневий темп"; -"menu_bar_layout_token_auto_pace" = "Автоматичний темп"; +"menu_bar_layout_token_session_pace" = "Сесія ±%"; +"menu_bar_layout_token_weekly_pace" = "Щотижня ±%"; +"menu_bar_layout_token_auto_pace" = "Авто ±%"; "Session pace" = "Темп сесії"; "Weekly pace" = "Тижневий темп"; "Pace" = "Темп"; diff --git a/Sources/CodexBar/Resources/vi.lproj/Localizable.strings b/Sources/CodexBar/Resources/vi.lproj/Localizable.strings index 44912c1c55..6230a8c880 100644 --- a/Sources/CodexBar/Resources/vi.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/vi.lproj/Localizable.strings @@ -557,6 +557,11 @@ "menu_bar_inactive_display_contrast_title" = "Cải thiện khả năng hiển thị trên màn hình không hoạt động"; "menu_bar_usage_colors_title" = "Mức dùng theo màu"; "menu_bar_usage_colors_subtitle" = "Tô màu biểu tượng thanh menu từ xanh lá sang đỏ khi mức sử dụng tăng."; +"menu_bar_usage_colors_target_title" = "Màu áp dụng cho"; +"menu_bar_usage_colors_target_subtitle" = "Chỉ Biểu tượng + phần trăm. Chọn phần nào của thanh menu nhận màu mức dùng."; +"menu_bar_usage_colors_target_icon" = "Biểu tượng"; +"menu_bar_usage_colors_target_usage" = "Giá trị mức dùng"; +"menu_bar_usage_colors_target_everything" = "Toàn bộ thanh"; "menu_bar_inactive_display_contrast_subtitle" = "Sử dụng hiển thị tương phản cao để biểu tượng và chỉ số vẫn dễ đọc trên các màn hình khác."; "menu_bar_style_critters" = "Sinh vật"; "menu_bar_style_bars" = "Thanh đo"; @@ -1328,9 +1333,9 @@ "menu_bar_layout_token_weekly" = "Hàng tuần %"; "menu_bar_layout_token_auto" = "% tự động"; "menu_bar_layout_token_bar" = "Thanh sử dụng"; -"menu_bar_layout_token_session_pace" = "Tốc độ phiên"; -"menu_bar_layout_token_weekly_pace" = "Tốc độ hàng tuần"; -"menu_bar_layout_token_auto_pace" = "Tốc độ tự động"; +"menu_bar_layout_token_session_pace" = "Phiên ±%"; +"menu_bar_layout_token_weekly_pace" = "Hàng tuần ±%"; +"menu_bar_layout_token_auto_pace" = "±% tự động"; "Session pace" = "Tốc độ phiên"; "Weekly pace" = "Tốc độ hàng tuần"; "Pace" = "Tốc độ"; diff --git a/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings b/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings index 8be578a4dc..0b52bfdc3d 100644 --- a/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings @@ -539,6 +539,11 @@ "menu_bar_inactive_display_contrast_title" = "提高非活跃显示器上的可见性"; "menu_bar_usage_colors_title" = "用量颜色标识"; "menu_bar_usage_colors_subtitle" = "随着用量上升,菜单栏图标由绿变红。"; +"menu_bar_usage_colors_target_title" = "颜色应用于"; +"menu_bar_usage_colors_target_subtitle" = "仅限“图标 + 百分比”。选择菜单栏中哪些部分使用用量颜色。"; +"menu_bar_usage_colors_target_icon" = "图标"; +"menu_bar_usage_colors_target_usage" = "用量数值"; +"menu_bar_usage_colors_target_everything" = "整个菜单栏"; "menu_bar_inactive_display_contrast_subtitle" = "使用高对比度绘制,让其他显示器上的图标和指标仍清晰可读。"; "menu_bar_style_critters" = "小动物"; "menu_bar_style_bars" = "进度条"; @@ -1303,9 +1308,9 @@ "menu_bar_layout_token_weekly" = "每周 %"; "menu_bar_layout_token_auto" = "自动 %"; "menu_bar_layout_token_bar" = "用量条"; -"menu_bar_layout_token_session_pace" = "会话节奏"; -"menu_bar_layout_token_weekly_pace" = "每周节奏"; -"menu_bar_layout_token_auto_pace" = "自动节奏"; +"menu_bar_layout_token_session_pace" = "会话 ±%"; +"menu_bar_layout_token_weekly_pace" = "每周 ±%"; +"menu_bar_layout_token_auto_pace" = "自动 ±%"; "Session pace" = "会话节奏"; "Weekly pace" = "每周节奏"; "Pace" = "节奏"; diff --git a/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings b/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings index fd0b09417c..a4ed47ad02 100644 --- a/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings @@ -560,6 +560,11 @@ "menu_bar_inactive_display_contrast_title" = "改善非使用中顯示器上的可見度"; "menu_bar_usage_colors_title" = "用量顏色標示"; "menu_bar_usage_colors_subtitle" = "隨著用量上升,選單列圖示由綠轉紅。"; +"menu_bar_usage_colors_target_title" = "顏色套用於"; +"menu_bar_usage_colors_target_subtitle" = "僅限「圖示 + 百分比」。選擇選單列中哪些部分套用用量顏色。"; +"menu_bar_usage_colors_target_icon" = "圖示"; +"menu_bar_usage_colors_target_usage" = "用量數值"; +"menu_bar_usage_colors_target_everything" = "整個選單列"; "menu_bar_inactive_display_contrast_subtitle" = "使用高對比顯示,讓其他顯示器上的圖示與指標保持清晰可讀。"; "menu_bar_style_critters" = "小動物"; "menu_bar_style_bars" = "進度條"; @@ -1358,9 +1363,9 @@ "menu_bar_layout_token_weekly" = "每週 %"; "menu_bar_layout_token_auto" = "自動 %"; "menu_bar_layout_token_bar" = "用量列"; -"menu_bar_layout_token_session_pace" = "工作階段進度"; -"menu_bar_layout_token_weekly_pace" = "每週進度"; -"menu_bar_layout_token_auto_pace" = "自動進度"; +"menu_bar_layout_token_session_pace" = "工作階段 ±%"; +"menu_bar_layout_token_weekly_pace" = "每週 ±%"; +"menu_bar_layout_token_auto_pace" = "自動 ±%"; "Session pace" = "工作階段進度"; "Weekly pace" = "每週進度"; "Pace" = "進度"; diff --git a/Sources/CodexBar/SettingsStore+Defaults.swift b/Sources/CodexBar/SettingsStore+Defaults.swift index e514518fc9..a0ecc68ace 100644 --- a/Sources/CodexBar/SettingsStore+Defaults.swift +++ b/Sources/CodexBar/SettingsStore+Defaults.swift @@ -317,6 +317,14 @@ extension SettingsStore { } } + var menuBarUsageColorTarget: MenuBarUsageColorTarget { + get { MenuBarUsageColorTarget(rawValue: self.defaultsState.menuBarUsageColorTargetRaw) ?? .usage } + set { + self.defaultsState.menuBarUsageColorTargetRaw = newValue.rawValue + self.userDefaults.set(newValue.rawValue, forKey: "menuBarUsageColorTarget") + } + } + var menuBarHighContrastOnInactiveDisplays: Bool { get { self.defaultsState.menuBarHighContrastOnInactiveDisplays } set { diff --git a/Sources/CodexBar/SettingsStore+MenuObservation.swift b/Sources/CodexBar/SettingsStore+MenuObservation.swift index bb8eab9984..8c2dd4e9b7 100644 --- a/Sources/CodexBar/SettingsStore+MenuObservation.swift +++ b/Sources/CodexBar/SettingsStore+MenuObservation.swift @@ -29,6 +29,7 @@ extension SettingsStore { _ = self.menuBarShowsBrandIconWithPercent _ = self.menuBarHidesCritters _ = self.menuBarUsageColorsEnabled + _ = self.menuBarUsageColorTarget _ = self.menuBarHighContrastOnInactiveDisplays _ = self.menuBarShowsHighestUsage _ = self.menuBarDisplayMode diff --git a/Sources/CodexBar/SettingsStore.swift b/Sources/CodexBar/SettingsStore.swift index 0ef2f6caab..ac0c929de9 100644 --- a/Sources/CodexBar/SettingsStore.swift +++ b/Sources/CodexBar/SettingsStore.swift @@ -451,6 +451,9 @@ extension SettingsStore { // menu bar; colored icons are the reason this fork exists, so it defaults on here. let menuBarUsageColorsEnabled = userDefaults .object(forKey: "menuBarUsageColorsEnabled") as? Bool ?? true + // Only the layout strip can honor anything but `.icon`; the legacy meter path has one glyph to tint. + let menuBarUsageColorTargetRaw = userDefaults.string(forKey: "menuBarUsageColorTarget") + ?? MenuBarUsageColorTarget.usage.rawValue let menuBarHighContrastOnInactiveDisplays = userDefaults.object( forKey: "menuBarHighContrastOnInactiveDisplays") as? Bool ?? false let menuBarDisplayModeRaw = userDefaults.string(forKey: "menuBarDisplayMode") @@ -565,6 +568,7 @@ extension SettingsStore { menuBarShowsBrandIconWithPercent: menuBarShowsBrandIconWithPercent, menuBarHidesCritters: menuBarHidesCritters, menuBarUsageColorsEnabled: menuBarUsageColorsEnabled, + menuBarUsageColorTargetRaw: menuBarUsageColorTargetRaw, menuBarHighContrastOnInactiveDisplays: menuBarHighContrastOnInactiveDisplays, menuBarDisplayModeRaw: menuBarDisplayModeRaw, menuBarShowsResetTimeWhenExhausted: menuBarShowsResetTimeWhenExhausted, diff --git a/Sources/CodexBar/SettingsStoreState.swift b/Sources/CodexBar/SettingsStoreState.swift index da6f00c4bd..de2f530fb6 100644 --- a/Sources/CodexBar/SettingsStoreState.swift +++ b/Sources/CodexBar/SettingsStoreState.swift @@ -30,6 +30,7 @@ struct SettingsDefaultsState { var menuBarShowsBrandIconWithPercent: Bool var menuBarHidesCritters: Bool var menuBarUsageColorsEnabled: Bool + var menuBarUsageColorTargetRaw: String var menuBarHighContrastOnInactiveDisplays: Bool var menuBarDisplayModeRaw: String? var menuBarShowsResetTimeWhenExhausted: Bool diff --git a/Sources/CodexBar/StatusItemController+IconObservation.swift b/Sources/CodexBar/StatusItemController+IconObservation.swift index 8aac9d0f22..2e43b636fa 100644 --- a/Sources/CodexBar/StatusItemController+IconObservation.swift +++ b/Sources/CodexBar/StatusItemController+IconObservation.swift @@ -30,6 +30,7 @@ extension StatusItemController { "brandPercent=\(showBrandPercent ? "1" : "0")", "hideCritters=\(self.settings.menuBarHidesCritters ? "1" : "0")", "usageColors=\(self.settings.menuBarUsageColorsEnabled ? "1" : "0")", + "usageColorTarget=\(self.settings.menuBarUsageColorTarget.rawValue)", "needsAnimation=\(self.needsMenuBarIconAnimation() ? "1" : "0")", providerSignatures, ].joined(separator: "|") diff --git a/Sources/CodexBar/StatusItemController+MenuBarLayout.swift b/Sources/CodexBar/StatusItemController+MenuBarLayout.swift index cd87f38c65..7239f6e2bb 100644 --- a/Sources/CodexBar/StatusItemController+MenuBarLayout.swift +++ b/Sources/CodexBar/StatusItemController+MenuBarLayout.swift @@ -35,7 +35,10 @@ extension StatusItemController { showUsed: self.settings.usageBarsShowUsed, appearanceName: appearanceName, isDebugApp: Self.isDebugApp(bundleIdentifier: Bundle.main.bundleIdentifier), - now: minute) + now: minute, + usageColorTarget: self.settings.menuBarUsageColorsEnabled + ? self.settings.menuBarUsageColorTarget + : nil) let rendered = self.menuBarLayoutRenderer.render( layout: resolution.layout, data: data, @@ -64,7 +67,7 @@ extension StatusItemController { let runsOut = weeklyPaceDetail?.rightLabel let sessionPace = windows.session .flatMap { UsagePaceText.sessionDetail(provider: provider, window: $0, now: now) }? - .leftLabel + .compactLabel let costStrings = self.menuBarLayoutCostStrings(provider: provider, now: now) let providerName = L(self.store.metadata(for: provider).displayName) let accountLabel = self.menuBarLayoutAccountLabel(provider: provider, snapshot: snapshot) @@ -77,7 +80,7 @@ extension StatusItemController { weekly: MenuBarLayoutRenderWindow(windows.weekly), automatic: MenuBarLayoutRenderWindow(windows.automatic), sessionPace: sessionPace, - weeklyPace: weeklyPaceDetail?.leftLabel, + weeklyPace: weeklyPaceDetail?.compactLabel, runsOut: runsOut, costToday: costStrings.today, cost30d: costStrings.last30Days) diff --git a/Sources/CodexBar/UsagePaceText.swift b/Sources/CodexBar/UsagePaceText.swift index 76cd6e833a..d91052919f 100644 --- a/Sources/CodexBar/UsagePaceText.swift +++ b/Sources/CodexBar/UsagePaceText.swift @@ -4,6 +4,9 @@ import Foundation enum UsagePaceText { struct WeeklyDetail { let leftLabel: String + /// Menu bar form of `leftLabel`: `+20%` when under the expected burn, `-8%` when over, `0%` on pace. + /// The prose form is fine in the dropdown but eats menu bar width the strip does not have. + let compactLabel: String let rightLabel: String? let expectedUsedPercent: Double let stage: UsagePace.Stage @@ -31,6 +34,7 @@ enum UsagePaceText { static func weeklyDetail(provider: UsageProvider, pace: UsagePace, now: Date = .init()) -> WeeklyDetail { WeeklyDetail( leftLabel: self.detailLeftLabel(for: pace), + compactLabel: self.detailCompactLabel(for: pace), rightLabel: self.detailRightLabel(for: pace, provider: provider, context: .weekly, now: now), expectedUsedPercent: pace.expectedUsedPercent, stage: pace.stage) @@ -88,6 +92,25 @@ enum UsagePaceText { } } + /// Signed counterpart to `detailLeftLabel`, sharing its stage-first branching so the two never disagree. + /// Sign reads as budget: `+` is quota held back against the expected burn, `-` is quota spent ahead of it. + /// Built from `UsageFormatter.percentString` rather than a catalog string so the `%` placement stays + /// consistent with every other percentage in the strip. + private static func detailCompactLabel(for pace: UsagePace) -> String { + let deltaValue = abs(pace.deltaPercent).rounded() + if deltaValue == 0 { + return UsageFormatter.percentString(0) + } + switch pace.stage { + case .onTrack: + return UsageFormatter.percentString(0) + case .slightlyAhead, .ahead, .farAhead: + return "-\(UsageFormatter.percentString(deltaValue))" + case .slightlyBehind, .behind, .farBehind: + return "+\(UsageFormatter.percentString(deltaValue))" + } + } + private static func detailRightLabel( for pace: UsagePace, provider: UsageProvider, @@ -177,6 +200,7 @@ enum UsagePaceText { guard let pace = sessionPace(provider: provider, window: window, now: now) else { return nil } return WeeklyDetail( leftLabel: Self.detailLeftLabel(for: pace), + compactLabel: Self.detailCompactLabel(for: pace), rightLabel: Self.detailRightLabel(for: pace, provider: provider, context: .session, now: now), expectedUsedPercent: pace.expectedUsedPercent, stage: pace.stage) diff --git a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift index ca353e24bb..26fcf2ffa1 100644 --- a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift +++ b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift @@ -1,5 +1,5 @@ // Generated by Scripts/regenerate-codex-parser-hash.sh. Do not edit by hand. enum CodexParserHash { - static let value = "3aa49b47f4b78e13" + static let value = "53fee87090b4369c" } diff --git a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageJsonl.swift b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageJsonl.swift index 4911826e5b..2078389150 100644 --- a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageJsonl.swift +++ b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageJsonl.swift @@ -298,7 +298,17 @@ enum CostUsageJsonl { var current = resumeState?.prefix ?? Data() current.reserveCapacity(4 * 1024) var lineBytes = resumeState?.lineBytes ?? 0 - var truncated = resumeState?.truncated ?? false + // Local carry: written as a plain declaration plus a conditional assignment rather than + // `resumeState?.truncated ?? false`. The `??` form lets the Swift 6.2.3 optimizer sink this + // Bool's alloc_box into both arms of the resumeState branch, so the box reaches the nested + // functions below as a block argument. Those captures take a lexical borrow, and a lexical + // borrow of a block-argument box is illegal SIL — swift-frontend aborts with "Lexical borrows + // of SILBoxTypes must be of vars or captures" in release (-O) builds only. Allocating the box + // unconditionally in the entry block leaves nothing to sink. + var truncated = false + if let resumeState { + truncated = resumeState.truncated + } var bytesRead: Int64 = 0 var lineStartOffset = resumeState?.lineStartOffset ?? startOffset var committedOffset = lineStartOffset diff --git a/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift b/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift index 5bce8e035f..fbbf7b4589 100644 --- a/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift +++ b/Tests/CodexBarTests/MenuBarLayoutRendererTests.swift @@ -17,11 +17,11 @@ struct MenuBarLayoutRendererTests { let expected: [(MenuBarLayoutToken, String)] = [ (.providerName, "Codex"), (.accountLabel, "user@example.com"), - (.percent(window: .session), "5h 25%"), + (.percent(window: .session), "S 25%"), (.percent(window: .weekly), "W 60%"), (.percent(window: .automatic), "50%"), (.usageBar, "▮▮▯"), - (.pace(window: .session), "5h 10% in reserve"), + (.pace(window: .session), "S 10% in reserve"), (.pace(window: .weekly), "W 4% in deficit"), (.pace(window: .automatic), "10% in reserve"), (.resetCountdown, "in 2h"), @@ -146,6 +146,89 @@ struct MenuBarLayoutRendererTests { #expect(output.attributedTitle.string == "4% in deficit") } + @Test + func `pace drops its prefix when the strip already labels that window`() { + let renderer = MenuBarLayoutRenderer() + let deduped = renderer.render( + layout: MenuBarLayout(lines: [[.percent(window: .session), .pace(window: .session)]]), + data: self.data(), + icon: nil, + options: self.options()) + // The weekly pace keeps its prefix: no weekly percent token labels that window here. + let unlabeled = renderer.render( + layout: MenuBarLayout(lines: [[.percent(window: .session), .pace(window: .weekly)]]), + data: self.data(), + icon: nil, + options: self.options()) + + #expect(deduped.attributedTitle.string == "S 25%\u{2009}10% in reserve") + #expect(unlabeled.attributedTitle.string == "S 25%\u{2009}W 4% in deficit") + } + + @Test + func `usage color target decides which tokens carry the tint`() throws { + let renderer = MenuBarLayoutRenderer() + let icon = NSImage(size: NSSize(width: 16, height: 16)) + icon.isTemplate = true + let layout = MenuBarLayout(lines: [[.icon, .accountLabel, .percent(window: .automatic)]]) + // `data()` reports 50% used, which is inside the tint ramp rather than at either clamp. + let tint = try #require(MenuBarUsageTint.color(forUsedPercent: 50)) + + func colors(target: MenuBarUsageColorTarget?) throws -> (account: NSColor?, percent: NSColor?) { + let output = renderer.render( + layout: layout, + data: self.data(), + icon: icon, + options: self.options(usageColorTarget: target)) + let title = output.attributedTitle + let accountIndex = (title.string as NSString).range(of: "user@example.com").location + let percentIndex = (title.string as NSString).range(of: "50%").location + return ( + title.attribute(.foregroundColor, at: accountIndex, effectiveRange: nil) as? NSColor, + title.attribute(.foregroundColor, at: percentIndex, effectiveRange: nil) as? NSColor) + } + + let off = try colors(target: nil) + #expect(off.account == .controlTextColor) + #expect(off.percent == .controlTextColor) + + let usageOnly = try colors(target: .usage) + #expect(usageOnly.account == .controlTextColor) + #expect(usageOnly.percent == tint) + + let everything = try colors(target: .everything) + #expect(everything.account == tint) + #expect(everything.percent == tint) + + let iconOnly = try colors(target: .icon) + #expect(iconOnly.account == .controlTextColor) + #expect(iconOnly.percent == .controlTextColor) + } + + @Test + func `tinted icon stops being a template so AppKit keeps the color`() throws { + let renderer = MenuBarLayoutRenderer() + let icon = NSImage(size: NSSize(width: 16, height: 16)) + icon.isTemplate = true + + func attachmentImage(target: MenuBarUsageColorTarget?) throws -> NSImage { + let output = renderer.render( + layout: MenuBarLayout(lines: [[.icon]]), + data: self.data(), + icon: icon, + options: self.options(usageColorTarget: target)) + let attachment = try #require( + output.attributedTitle.attribute(.attachment, at: 0, effectiveRange: nil) as? NSTextAttachment) + return try #require(attachment.image) + } + + #expect(try attachmentImage(target: nil).isTemplate) + // Every target tints the icon, so none of them may leave it a template. + for target in MenuBarUsageColorTarget.allCases { + #expect(try !attachmentImage(target: target).isTemplate) + } + } + @Test func `two line title stays within menu bar height`() throws { let renderer = MenuBarLayoutRenderer() @@ -158,7 +241,7 @@ struct MenuBarLayoutRendererTests { with: NSSize(width: 200, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin, .usesFontLeading]) - #expect(output.attributedTitle.string == "5h 25%\nW 60%") + #expect(output.attributedTitle.string == "S 25%\nW 60%") #expect(output.accessibilityLabel.contains(L("menu_bar_layout_line", 2))) #expect(bounds.height <= 22) } @@ -241,7 +324,8 @@ struct MenuBarLayoutRendererTests { showUsed: false, appearanceName: "aqua", isDebugApp: false, - now: self.now)) + now: self.now, + usageColorTarget: nil)) #expect(output.attributedTitle.string == "▮▮▮") } @@ -287,7 +371,8 @@ struct MenuBarLayoutRendererTests { showUsed: options.showUsed, appearanceName: options.appearanceName, isDebugApp: options.isDebugApp, - now: options.now) + now: options.now, + usageColorTarget: options.usageColorTarget) let output = renderer.render( layout: MenuBarLayout(lines: [[.icon, .percent(window: .automatic)]]), data: self.data(), @@ -327,14 +412,18 @@ struct MenuBarLayoutRendererTests { cost30d: "$20.00") } - private func options() -> MenuBarLayoutRenderOptions { + private func options( + usageColorTarget: MenuBarUsageColorTarget? = nil) + -> MenuBarLayoutRenderOptions + { MenuBarLayoutRenderOptions( size: .regular, highContrast: false, showUsed: true, appearanceName: "aqua", isDebugApp: false, - now: self.now) + now: self.now, + usageColorTarget: usageColorTarget) } private func averageBrightness( diff --git a/Tests/CodexBarTests/UsagePaceTextTests.swift b/Tests/CodexBarTests/UsagePaceTextTests.swift index a703689995..349174bb7a 100644 --- a/Tests/CodexBarTests/UsagePaceTextTests.swift +++ b/Tests/CodexBarTests/UsagePaceTextTests.swift @@ -242,10 +242,32 @@ struct UsagePaceTextTests { #expect(detail != nil) #expect(detail?.leftLabel == "20% in deficit") + // Menu bar form: spending ahead of the expected burn reads as negative headroom. + #expect(detail?.compactLabel == "-20%") #expect(detail?.rightLabel == "Projected empty in 45m") #expect(detail?.stage == .farAhead) } + @Test + func `compact pace label signs reserve and deficit opposite ways`() { + let now = Date(timeIntervalSince1970: 0) + /// 300-minute window, 2h remaining => 3h elapsed, expected 60%. + func compactLabel(usedPercent: Double) -> String? { + UsagePaceText.sessionDetail( + provider: .claude, + window: RateWindow( + usedPercent: usedPercent, + windowMinutes: 300, + resetsAt: now.addingTimeInterval(2 * 3600), + resetDescription: nil), + now: now)?.compactLabel + } + + #expect(compactLabel(usedPercent: 40) == "+20%") + #expect(compactLabel(usedPercent: 60) == "0%") + #expect(compactLabel(usedPercent: 80) == "-20%") + } + @Test func `Claude session pace does not show Codex headroom`() { let now = Date(timeIntervalSince1970: 0)