Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions Sources/CodexBar/IconRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,15 @@ enum IconRenderer {
yRadius: Self.grid.pt(max(0, cornerRadiusPx - insetPx)))
strokePath.lineWidth = CGFloat(strokeWidthPx) / Self.outputScale
baseFill.withAlphaComponent(trackStrokeAlpha * alpha).setStroke()
strokePath.stroke()
if let ctx = NSGraphicsContext.current?.cgContext {
ctx.saveGState()
ctx.setLineJoin(.round)
ctx.setLineCap(.round)
strokePath.stroke()
ctx.restoreGState()
} else {
strokePath.stroke()
}

// Fill: clip to the capsule and paint a left-to-right rect so the progress edge is straight.
if let remaining {
Expand Down Expand Up @@ -462,7 +470,7 @@ enum IconRenderer {
}
}

if addAntigravityTwist {
if addAntigravityTwist, style != .cursor {
let dotSizePx = 3
let dotOffsetXPx = rectPx.x + rectPx.w + 2
let dotOffsetYPx = rectPx.y + rectPx.h - 2
Expand Down
98 changes: 98 additions & 0 deletions Sources/CodexBar/MenuBarMetricWindowResolver.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import CodexBarCore
import Foundation

enum MenuBarMetricWindowResolver {
static func rateWindow(
preference: MenuBarMetricPreference,
provider: UsageProvider,
snapshot: UsageSnapshot?,
supportsAverage: Bool)
-> RateWindow?
{
self.rateWindow(
lane: MenuBarIconLane(rawValue: preference.rawValue) ?? .automatic,
provider: provider,
snapshot: snapshot,
supportsAverage: supportsAverage)
}

static func rateWindow(
lane: MenuBarIconLane,
provider: UsageProvider,
snapshot: UsageSnapshot?,
supportsAverage: Bool)
-> RateWindow?
{
guard let snapshot, lane != .none else { return nil }
switch lane {
case .none:
return nil
case .tertiary:
guard provider == .cursor else {
return snapshot.primary ?? snapshot.secondary
}
return snapshot.tertiary ?? snapshot.secondary ?? snapshot.primary
case .primary:
return snapshot.primary ?? snapshot.secondary
case .secondary:
return snapshot.secondary ?? snapshot.primary
case .average:
guard supportsAverage,
let primary = snapshot.primary,
let secondary = snapshot.secondary
else {
return snapshot.primary ?? snapshot.secondary
}
let usedPercent = (primary.usedPercent + secondary.usedPercent) / 2
return RateWindow(usedPercent: usedPercent, windowMinutes: nil, resetsAt: nil, resetDescription: nil)
case .automatic:
if provider == .factory || provider == .kimi {
return snapshot.secondary ?? snapshot.primary
}
if provider == .copilot,
let primary = snapshot.primary,
let secondary = snapshot.secondary
{
return primary.usedPercent >= secondary.usedPercent ? primary : secondary
}
if provider == .cursor {
return Self.mostConstrainedWindow(
primary: snapshot.primary,
secondary: snapshot.secondary,
tertiary: snapshot.tertiary)
}
return snapshot.primary ?? snapshot.secondary
}
}

/// Second bar when **Automatic** is chosen for the bottom slot (or legacy paired automatic).
static func secondAutomaticWindow(provider: UsageProvider, snapshot: UsageSnapshot) -> RateWindow? {
if provider == .factory || provider == .kimi {
return snapshot.primary
}
if provider == .copilot,
let primaryWin = snapshot.primary,
let secondaryWin = snapshot.secondary
{
let ordered = [primaryWin, secondaryWin].sorted { $0.usedPercent > $1.usedPercent }
return ordered.count >= 2 ? ordered[1] : nil
}
if provider == .cursor {
let ranked = [snapshot.primary, snapshot.secondary, snapshot.tertiary].compactMap(\.self)
.sorted { $0.usedPercent > $1.usedPercent }
return ranked.count >= 2 ? ranked[1] : nil
}
return snapshot.secondary
}

private static func mostConstrainedWindow(
primary: RateWindow?,
secondary: RateWindow?,
tertiary: RateWindow?)
-> RateWindow?
{
let windows = [primary, secondary, tertiary].compactMap(\.self)
guard !windows.isEmpty else { return nil }
return windows.max(by: { $0.usedPercent < $1.usedPercent })
}
}
12 changes: 8 additions & 4 deletions Sources/CodexBar/PreferencesProvidersPane+Testing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ extension ProvidersPane {
}

func _test_menuBarMetricPicker(for provider: UsageProvider) -> ProviderSettingsPickerDescriptor? {
self.menuBarMetricPicker(for: provider)
self.menuBarLanePickers(for: provider).first
}

func _test_menuBarLanePickers(for provider: UsageProvider) -> [ProviderSettingsPickerDescriptor] {
self.menuBarLanePickers(for: provider)
}

func _test_tokenAccountDescriptor(for provider: UsageProvider) -> ProviderSettingsTokenAccountsDescriptor? {
Expand Down Expand Up @@ -69,9 +73,9 @@ enum ProvidersPaneTestHarness {
_ = pane._test_providerSubtitle(.kimi)
_ = pane._test_providerSubtitle(.gemini)

_ = pane._test_menuBarMetricPicker(for: .codex)
_ = pane._test_menuBarMetricPicker(for: .gemini)
_ = pane._test_menuBarMetricPicker(for: .zai)
_ = pane._test_menuBarLanePickers(for: .codex)
_ = pane._test_menuBarLanePickers(for: .gemini)
_ = pane._test_menuBarLanePickers(for: .zai)

if let descriptor = pane._test_tokenAccountDescriptor(for: .claude) {
_ = descriptor.isVisible?()
Expand Down
134 changes: 92 additions & 42 deletions Sources/CodexBar/PreferencesProvidersPane.swift
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,11 @@ struct ProvidersPane: View {
let context = self.makeSettingsContext(provider: provider)
let providerPickers = impl.settingsPickers(context: context)
.filter { $0.isVisible?() ?? true }
if let menuBarPicker = self.menuBarMetricPicker(for: provider) {
return [menuBarPicker] + providerPickers
let lanePickers = self.menuBarLanePickers(for: provider)
if lanePickers.isEmpty {
return providerPickers
}
return providerPickers
return lanePickers + providerPickers
}

private func extraSettingsFields(for provider: UsageProvider) -> [ProviderSettingsFieldDescriptor] {
Expand Down Expand Up @@ -262,48 +263,97 @@ struct ProvidersPane: View {
})
}

func menuBarMetricPicker(for provider: UsageProvider) -> ProviderSettingsPickerDescriptor? {
if provider == .zai { return nil }
let options: [ProviderSettingsPickerOption]
func menuBarLanePickers(for provider: UsageProvider) -> [ProviderSettingsPickerDescriptor] {
if provider == .zai { return [] }

let metadata = self.store.metadata(for: provider)
let supportsAverage = self.settings.menuBarMetricSupportsAverage(for: provider)
let supportsTertiary = self.settings.menuBarMetricSupportsTertiary(for: provider)

func laneTitle(_ lane: MenuBarIconLane) -> String {
switch lane {
case .automatic: return "Automatic"
case .primary: return "Primary (\(metadata.sessionLabel))"
case .secondary: return "Secondary (\(metadata.weeklyLabel))"
case .tertiary:
let tertiaryTitle = metadata.opusLabel ?? MenuBarMetricPreference.tertiary.label
return "Tertiary (\(tertiaryTitle))"
case .average: return "Average (\(metadata.sessionLabel) + \(metadata.weeklyLabel))"
case .none: return "Hidden (dimmed track)"
}
}

if provider == .openrouter {
options = [
ProviderSettingsPickerOption(id: MenuBarMetricPreference.automatic.rawValue, title: "Automatic"),
ProviderSettingsPickerOption(
id: MenuBarMetricPreference.primary.rawValue,
title: "Primary (API key limit)"),
]
} else {
let metadata = self.store.metadata(for: provider)
let supportsAverage = self.settings.menuBarMetricSupportsAverage(for: provider)
var metricOptions: [ProviderSettingsPickerOption] = [
ProviderSettingsPickerOption(id: MenuBarMetricPreference.automatic.rawValue, title: "Automatic"),
ProviderSettingsPickerOption(
id: MenuBarMetricPreference.primary.rawValue,
title: "Primary (\(metadata.sessionLabel))"),
ProviderSettingsPickerOption(
id: MenuBarMetricPreference.secondary.rawValue,
title: "Secondary (\(metadata.weeklyLabel))"),
let topOptions: [MenuBarIconLane] = [.automatic, .primary]
let bottomOptions: [MenuBarIconLane] = [.none, .automatic, .primary]
return [
ProviderSettingsPickerDescriptor(
id: "menuBarIconTopLane",
title: "Menu bar top bar",
subtitle: "Top segment; also drives the optional percent label.",
binding: Binding(
get: { self.settings.menuBarIconTopLane(for: provider).rawValue },
set: { rawValue in
guard let lane = MenuBarIconLane(rawValue: rawValue) else { return }
self.settings.setMenuBarIconTopLane(lane, for: provider)
}),
options: topOptions.map { ProviderSettingsPickerOption(id: $0.rawValue, title: laneTitle($0)) },
isVisible: { true },
onChange: nil),
ProviderSettingsPickerDescriptor(
id: "menuBarIconBottomLane",
title: "Menu bar bottom bar",
subtitle: "Lower segment of the icon.",
binding: Binding(
get: { self.settings.menuBarIconBottomLane(for: provider).rawValue },
set: { rawValue in
guard let lane = MenuBarIconLane(rawValue: rawValue) else { return }
self.settings.setMenuBarIconBottomLane(lane, for: provider)
}),
options: bottomOptions.map { ProviderSettingsPickerOption(id: $0.rawValue, title: laneTitle($0)) },
isVisible: { true },
onChange: nil),
]
if supportsAverage {
metricOptions.append(ProviderSettingsPickerOption(
id: MenuBarMetricPreference.average.rawValue,
title: "Average (\(metadata.sessionLabel) + \(metadata.weeklyLabel))"))
}
options = metricOptions
}
return ProviderSettingsPickerDescriptor(
id: "menuBarMetric",
title: "Menu bar metric",
subtitle: "Choose which window drives the menu bar percent.",
binding: Binding(
get: { self.settings.menuBarMetricPreference(for: provider).rawValue },
set: { rawValue in
guard let preference = MenuBarMetricPreference(rawValue: rawValue) else { return }
self.settings.setMenuBarMetricPreference(preference, for: provider)
}),
options: options,
isVisible: { true },
onChange: nil)

var topLanes: [MenuBarIconLane] = [.automatic, .primary, .secondary]
if supportsTertiary {
topLanes.append(.tertiary)
}
if supportsAverage {
topLanes.append(.average)
}
var bottomLanes = topLanes
bottomLanes.append(.none)

return [
ProviderSettingsPickerDescriptor(
id: "menuBarIconTopLane",
title: "Menu bar top bar",
subtitle: "Top segment; also drives the optional percent label.",
binding: Binding(
get: { self.settings.menuBarIconTopLane(for: provider).rawValue },
set: { rawValue in
guard let lane = MenuBarIconLane(rawValue: rawValue) else { return }
self.settings.setMenuBarIconTopLane(lane, for: provider)
}),
options: topLanes.map { ProviderSettingsPickerOption(id: $0.rawValue, title: laneTitle($0)) },
isVisible: { true },
onChange: nil),
ProviderSettingsPickerDescriptor(
id: "menuBarIconBottomLane",
title: "Menu bar bottom bar",
subtitle: "Lower segment of the icon. Choose Hidden for a single-bar look.",
binding: Binding(
get: { self.settings.menuBarIconBottomLane(for: provider).rawValue },
set: { rawValue in
guard let lane = MenuBarIconLane(rawValue: rawValue) else { return }
self.settings.setMenuBarIconBottomLane(lane, for: provider)
}),
options: bottomLanes.map { ProviderSettingsPickerOption(id: $0.rawValue, title: laneTitle($0)) },
isVisible: { true },
onChange: nil),
]
}

func menuCardModel(for provider: UsageProvider) -> UsageMenuCardView.Model {
Expand Down
16 changes: 16 additions & 0 deletions Sources/CodexBar/SettingsStore+Defaults.swift
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,22 @@ extension SettingsStore {
}
}

var menuBarIconTopLanePreferencesRaw: [String: String] {
get { self.defaultsState.menuBarIconTopLanePreferencesRaw }
set {
self.defaultsState.menuBarIconTopLanePreferencesRaw = newValue
self.userDefaults.set(newValue, forKey: "menuBarIconTopLanePreferences")
}
}

var menuBarIconBottomLanePreferencesRaw: [String: String] {
get { self.defaultsState.menuBarIconBottomLanePreferencesRaw }
set {
self.defaultsState.menuBarIconBottomLanePreferencesRaw = newValue
self.userDefaults.set(newValue, forKey: "menuBarIconBottomLanePreferences")
}
}

var costUsageEnabled: Bool {
get { self.defaultsState.costUsageEnabled }
set {
Expand Down
2 changes: 2 additions & 0 deletions Sources/CodexBar/SettingsStore+MenuObservation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ extension SettingsStore {
_ = self.historicalTrackingEnabled
_ = self.showAllTokenAccountsInMenu
_ = self.menuBarMetricPreferencesRaw
_ = self.menuBarIconTopLanePreferencesRaw
_ = self.menuBarIconBottomLanePreferencesRaw
_ = self.costUsageEnabled
_ = self.hidePersonalInfo
_ = self.randomBlinkEnabled
Expand Down
Loading