History

fix: align menu selection colors

Peter Steinberger committed 10 months ago

Commit dedcd251 parent e9491c15 files · +69 −24Browse files

Unified · Split

5 files
  1. CHANGELOG.md +1 −1
  2. Sources/CodexBar/MenuCardView.swift +22 −17
  3. Sources/CodexBar/MenuHighlightStyle.swift +37 −0
  4. Sources/CodexBar/StatusItemController+Menu.swift +6 −4
  5. Sources/CodexBar/UsageProgressBar.swift +3 −2
CHANGELOG.md+1−1
@@ -1,7 +1,7 @@
1# Changelog1# Changelog
2 2
3## 0.12.1 — Unreleased3## 0.12.1 — Unreleased
4- (nothing yet)4- Menu: custom menu items now use full selection highlight with inverted white text and bars.
5 5
6## 0.12.0 — 2025-12-236## 0.12.0 — 2025-12-23
7- Widgets: add WidgetKit extension backed by a shared app‑group usage snapshot.7- Widgets: add WidgetKit extension backed by a shared app‑group usage snapshot.
Sources/CodexBar/MenuCardView.swift+22−17
@@ -64,6 +64,7 @@ struct UsageMenuCardView: View { · 63 unmodified lines
64 }64 }
65 65
66 let model: Model66 let model: Model
67 @Environment(\.menuItemHighlighted) private var isHighlighted
67 68
68 init(model: Model) {69 init(model: Model) {
69 self.model = model70 self.model = model
@@ -80,7 +81,7 @@ struct UsageMenuCardView: View { · 10 unmodified lines
80 if self.model.metrics.isEmpty {81 if self.model.metrics.isEmpty {
81 if let placeholder = self.model.placeholder {82 if let placeholder = self.model.placeholder {
82 Text(placeholder)83 Text(placeholder)
83 .foregroundStyle(.secondary)84 .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))
84 .font(.subheadline)85 .font(.subheadline)
85 }86 }
86 } else {87 } else {
@@ -107,13 +108,13 @@ struct UsageMenuCardView: View { · 20 unmodified lines
107 if let reset = metric.resetText {108 if let reset = metric.resetText {
108 Text(reset)109 Text(reset)
109 .font(.footnote)110 .font(.footnote)
110 .foregroundStyle(.secondary)111 .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))
111 }112 }
112 }113 }
113 if let detail = metric.detailText {114 if let detail = metric.detailText {
114 Text(detail)115 Text(detail)
115 .font(.footnote)116 .font(.footnote)
116 .foregroundStyle(.secondary)117 .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))
117 .lineLimit(1)118 .lineLimit(1)
118 }119 }
119 }120 }
@@ -145,14 +146,14 @@ struct UsageMenuCardView: View { · 25 unmodified lines
145 if let hint = tokenUsage.hintLine, !hint.isEmpty {146 if let hint = tokenUsage.hintLine, !hint.isEmpty {
146 Text(hint)147 Text(hint)
147 .font(.footnote)148 .font(.footnote)
148 .foregroundStyle(.secondary)149 .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))
149 .lineLimit(2)150 .lineLimit(2)
150 .fixedSize(horizontal: false, vertical: true)151 .fixedSize(horizontal: false, vertical: true)
151 }152 }
152 if let error = tokenUsage.errorLine, !error.isEmpty {153 if let error = tokenUsage.errorLine, !error.isEmpty {
153 Text(error)154 Text(error)
154 .font(.footnote)155 .font(.footnote)
155 .foregroundStyle(Color(nsColor: .systemRed))156 .foregroundStyle(MenuHighlightStyle.error(self.isHighlighted))
156 .lineLimit(2)157 .lineLimit(2)
157 .fixedSize(horizontal: false, vertical: true)158 .fixedSize(horizontal: false, vertical: true)
158 }159 }
@@ -175,6 +176,7 @@ struct UsageMenuCardView: View { · 16 unmodified lines
175 176
176private struct UsageMenuCardHeaderView: View {177private struct UsageMenuCardHeaderView: View {
177 let model: UsageMenuCardView.Model178 let model: UsageMenuCardView.Model
179 @Environment(\.menuItemHighlighted) private var isHighlighted
178 180
179 var body: some View {181 var body: some View {
180 VStack(alignment: .leading, spacing: 3) {182 VStack(alignment: .leading, spacing: 3) {
@@ -185,7 +187,7 @@ private struct UsageMenuCardHeaderView: View { · 4 unmodified lines
185 Spacer()187 Spacer()
186 Text(self.model.email)188 Text(self.model.email)
187 .font(.subheadline)189 .font(.subheadline)
188 .foregroundStyle(.secondary)190 .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))
189 }191 }
190 HStack(alignment: .firstTextBaseline) {192 HStack(alignment: .firstTextBaseline) {
191 Text(self.model.subtitleText)193 Text(self.model.subtitleText)
@@ -198,7 +200,7 @@ private struct UsageMenuCardHeaderView: View { · 6 unmodified lines
198 if let plan = self.model.planText {200 if let plan = self.model.planText {
199 Text(plan)201 Text(plan)
200 .font(.footnote)202 .font(.footnote)
201 .foregroundStyle(.secondary)203 .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))
202 .lineLimit(1)204 .lineLimit(1)
203 }205 }
204 }206 }
@@ -207,9 +209,9 @@ private struct UsageMenuCardHeaderView: View { · 2 unmodified lines
207 209
208 private var subtitleColor: Color {210 private var subtitleColor: Color {
209 switch self.model.subtitleStyle {211 switch self.model.subtitleStyle {
210 case .info: .secondary212 case .info: MenuHighlightStyle.secondary(self.isHighlighted)
211 case .loading: .secondary213 case .loading: MenuHighlightStyle.secondary(self.isHighlighted)
212 case .error: Color(nsColor: .systemRed)214 case .error: MenuHighlightStyle.error(self.isHighlighted)
213 }215 }
214 }216 }
215}217}
@@ -236,13 +238,14 @@ struct UsageMenuCardUsageSectionView: View { · 20 unmodified lines
236 let model: UsageMenuCardView.Model238 let model: UsageMenuCardView.Model
237 let showBottomDivider: Bool239 let showBottomDivider: Bool
238 let bottomPadding: CGFloat240 let bottomPadding: CGFloat
241 @Environment(\.menuItemHighlighted) private var isHighlighted
239 242
240 var body: some View {243 var body: some View {
241 VStack(alignment: .leading, spacing: 12) {244 VStack(alignment: .leading, spacing: 12) {
242 if self.model.metrics.isEmpty {245 if self.model.metrics.isEmpty {
243 if let placeholder = self.model.placeholder {246 if let placeholder = self.model.placeholder {
244 Text(placeholder)247 Text(placeholder)
245 .foregroundStyle(.secondary)248 .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))
246 .font(.subheadline)249 .font(.subheadline)
247 }250 }
248 } else {251 } else {
@@ -262,13 +265,13 @@ struct UsageMenuCardUsageSectionView: View { · 13 unmodified lines
262 if let reset = metric.resetText {265 if let reset = metric.resetText {
263 Text(reset)266 Text(reset)
264 .font(.footnote)267 .font(.footnote)
265 .foregroundStyle(.secondary)268 .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))
266 }269 }
267 }270 }
268 if let detail = metric.detailText {271 if let detail = metric.detailText {
269 Text(detail)272 Text(detail)
270 .font(.footnote)273 .font(.footnote)
271 .foregroundStyle(.secondary)274 .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))
272 .lineLimit(1)275 .lineLimit(1)
273 }276 }
274 }277 }
@@ -330,6 +333,7 @@ private struct CreditsBarContent: View { · 55 unmodified lines
330 let creditsRemaining: Double?333 let creditsRemaining: Double?
331 let hintText: String?334 let hintText: String?
332 let progressColor: Color335 let progressColor: Color
336 @Environment(\.menuItemHighlighted) private var isHighlighted
333 337
334 private var percentLeft: Double? {338 private var percentLeft: Double? {
335 guard let creditsRemaining else { return nil }339 guard let creditsRemaining else { return nil }
@@ -358,7 +362,7 @@ private struct CreditsBarContent: View { · 22 unmodified lines
358 Spacer()362 Spacer()
359 Text(self.scaleText)363 Text(self.scaleText)
360 .font(.caption)364 .font(.caption)
361 .foregroundStyle(.secondary)365 .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))
362 }366 }
363 } else {367 } else {
364 Text(self.creditsText)368 Text(self.creditsText)
@@ -367,7 +371,7 @@ private struct CreditsBarContent: View { · 2 unmodified lines
367 if let hintText, !hintText.isEmpty {371 if let hintText, !hintText.isEmpty {
368 Text(hintText)372 Text(hintText)
369 .font(.footnote)373 .font(.footnote)
370 .foregroundStyle(.secondary)374 .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))
371 .lineLimit(2)375 .lineLimit(2)
372 .fixedSize(horizontal: false, vertical: true)376 .fixedSize(horizontal: false, vertical: true)
373 }377 }
@@ -379,6 +383,7 @@ struct UsageMenuCardCostSectionView: View { · 5 unmodified lines
379 let model: UsageMenuCardView.Model383 let model: UsageMenuCardView.Model
380 let topPadding: CGFloat384 let topPadding: CGFloat
381 let bottomPadding: CGFloat385 let bottomPadding: CGFloat
386 @Environment(\.menuItemHighlighted) private var isHighlighted
382 387
383 var body: some View {388 var body: some View {
384 if let tokenUsage = self.model.tokenUsage {389 if let tokenUsage = self.model.tokenUsage {
@@ -393,14 +398,14 @@ struct UsageMenuCardCostSectionView: View { · 8 unmodified lines
393 if let hint = tokenUsage.hintLine, !hint.isEmpty {398 if let hint = tokenUsage.hintLine, !hint.isEmpty {
394 Text(hint)399 Text(hint)
395 .font(.footnote)400 .font(.footnote)
396 .foregroundStyle(.secondary)401 .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))
397 .lineLimit(2)402 .lineLimit(2)
398 .fixedSize(horizontal: false, vertical: true)403 .fixedSize(horizontal: false, vertical: true)
399 }404 }
400 if let error = tokenUsage.errorLine, !error.isEmpty {405 if let error = tokenUsage.errorLine, !error.isEmpty {
401 Text(error)406 Text(error)
402 .font(.footnote)407 .font(.footnote)
403 .foregroundStyle(Color(nsColor: .systemRed))408 .foregroundStyle(MenuHighlightStyle.error(self.isHighlighted))
404 .lineLimit(2)409 .lineLimit(2)
405 .fixedSize(horizontal: false, vertical: true)410 .fixedSize(horizontal: false, vertical: true)
406 }411 }
Sources/CodexBar/MenuHighlightStyle.swift+37−0
@@ -0,0 +1,37 @@
1import SwiftUI
2
3private struct MenuItemHighlightedKey: EnvironmentKey {
4 static let defaultValue = false
5}
6
7extension EnvironmentValues {
8 var menuItemHighlighted: Bool {
9 get { self[MenuItemHighlightedKey.self] }
10 set { self[MenuItemHighlightedKey.self] = newValue }
11 }
12}
13
14enum MenuHighlightStyle {
15 static let selectionBackground = Color(nsColor: .controlAccentColor)
16 static let selectionText = Color.white
17
18 static func primary(_ highlighted: Bool) -> Color {
19 highlighted ? Self.selectionText : .primary
20 }
21
22 static func secondary(_ highlighted: Bool) -> Color {
23 highlighted ? Self.selectionText : .secondary
24 }
25
26 static func error(_ highlighted: Bool) -> Color {
27 highlighted ? Self.selectionText : Color(nsColor: .systemRed)
28 }
29
30 static func progressTrack(_ highlighted: Bool) -> Color {
31 highlighted ? Self.selectionText.opacity(0.35) : Color.secondary.opacity(0.25)
32 }
33
34 static func progressTint(_ highlighted: Bool, fallback: Color) -> Color {
35 highlighted ? Self.selectionText : fallback
36 }
37}
Sources/CodexBar/StatusItemController+Menu.swift+6−4
@@ -469,6 +469,8 @@ extension StatusItemController { · 468 unmodified lines
469 469
470 var body: some View {470 var body: some View {
471 self.content471 self.content
472 .environment(\.menuItemHighlighted, self.highlightState.isHighlighted)
473 .foregroundStyle(MenuHighlightStyle.primary(self.highlightState.isHighlighted))
472 .background(alignment: .topLeading) {474 .background(alignment: .topLeading) {
473 if self.highlightState.isHighlighted {475 if self.highlightState.isHighlighted {
474 self.highlightBackground476 self.highlightBackground
@@ -478,7 +480,7 @@ extension StatusItemController { · 3 unmodified lines
478 if self.showsSubmenuIndicator {480 if self.showsSubmenuIndicator {
479 Image(systemName: "chevron.right")481 Image(systemName: "chevron.right")
480 .font(.caption2.weight(.semibold))482 .font(.caption2.weight(.semibold))
481 .foregroundStyle(.secondary)483 .foregroundStyle(MenuHighlightStyle.secondary(self.highlightState.isHighlighted))
482 .padding(.top, 8)484 .padding(.top, 8)
483 .padding(.trailing, 10)485 .padding(.trailing, 10)
484 }486 }
@@ -492,7 +494,7 @@ extension StatusItemController { · 7 unmodified lines
492 let bottomInset: CGFloat = 1494 let bottomInset: CGFloat = 1
493 let height = max(0, proxy.size.height - self.highlightExclusionHeight - topInset - bottomInset)495 let height = max(0, proxy.size.height - self.highlightExclusionHeight - topInset - bottomInset)
494 RoundedRectangle(cornerRadius: 6, style: .continuous)496 RoundedRectangle(cornerRadius: 6, style: .continuous)
495 .fill(Color(nsColor: .controlAccentColor).opacity(0.18))497 .fill(MenuHighlightStyle.selectionBackground)
496 .frame(height: height, alignment: .top)498 .frame(height: height, alignment: .top)
497 .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)499 .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
498 .padding(.horizontal, 6)500 .padding(.horizontal, 6)
@@ -676,9 +678,9 @@ private final class ProviderSwitcherView: NSView { · 177 unmodified lines
676 private let segments: [Segment]678 private let segments: [Segment]
677 private let onSelect: (UsageProvider) -> Void679 private let onSelect: (UsageProvider) -> Void
678 private var buttons: [NSButton] = []680 private var buttons: [NSButton] = []
679 private let selectedBackground = NSColor.labelColor.withAlphaComponent(0.12).cgColor681 private let selectedBackground = NSColor.controlAccentColor.cgColor
680 private let unselectedBackground = NSColor.clear.cgColor682 private let unselectedBackground = NSColor.clear.cgColor
681 private let selectedTextColor = NSColor.labelColor683 private let selectedTextColor = NSColor.white
682 private let unselectedTextColor = NSColor.secondaryLabelColor684 private let unselectedTextColor = NSColor.secondaryLabelColor
683 685
684 init(686 init(
Sources/CodexBar/UsageProgressBar.swift+3−2
@@ -5,6 +5,7 @@ struct UsageProgressBar: View { · 4 unmodified lines
5 let percent: Double5 let percent: Double
6 let tint: Color6 let tint: Color
7 let accessibilityLabel: String7 let accessibilityLabel: String
8 @Environment(\.menuItemHighlighted) private var isHighlighted
8 9
9 private var clamped: Double {10 private var clamped: Double {
10 min(100, max(0, self.percent))11 min(100, max(0, self.percent))
@@ -15,9 +16,9 @@ struct UsageProgressBar: View { · 4 unmodified lines
15 let fillWidth = proxy.size.width * self.clamped / 10016 let fillWidth = proxy.size.width * self.clamped / 100
16 ZStack(alignment: .leading) {17 ZStack(alignment: .leading) {
17 Capsule()18 Capsule()
18 .fill(Color.secondary.opacity(0.25))19 .fill(MenuHighlightStyle.progressTrack(self.isHighlighted))
19 Capsule()20 Capsule()
20 .fill(self.tint)21 .fill(MenuHighlightStyle.progressTint(self.isHighlighted, fallback: self.tint))
21 .frame(width: fillWidth)22 .frame(width: fillWidth)
22 }23 }
23 }24 }