import CodexBarCoreimport SwiftUI @MainActorstruct DisplayPane: View { private static let maxOverviewProviders = SettingsStore.mergedOverviewProviderLimit @State private var isOverviewProviderPopoverPresented = false @Bindable var settings: SettingsStore @Bindable var store: UsageStore var body: some View { ScrollView(.vertical, showsIndicators: true) { VStack(alignment: .leading, spacing: 16) { SettingsSection(contentSpacing: 12) { Text("Menu bar") .font(.caption) .foregroundStyle(.secondary) .textCase(.uppercase) PreferenceToggleRow( title: "Merge Icons", subtitle: "Use a single menu bar icon with a provider switcher.", binding: self.$settings.mergeIcons) PreferenceToggleRow( title: "Switcher shows icons", subtitle: "Show provider icons in the switcher (otherwise show a weekly progress line).", binding: self.$settings.switcherShowsIcons) .disabled(!self.settings.mergeIcons) .opacity(self.settings.mergeIcons ? 1 : 0.5) PreferenceToggleRow( title: "Show most-used provider", subtitle: "Menu bar auto-shows the provider closest to its rate limit.", binding: self.$settings.menuBarShowsHighestUsage) .disabled(!self.settings.mergeIcons) .opacity(self.settings.mergeIcons ? 1 : 0.5) PreferenceToggleRow( title: "Menu bar shows percent", subtitle: "Replace critter bars with provider branding icons and a percentage.", binding: self.$settings.menuBarShowsBrandIconWithPercent) HStack(alignment: .top, spacing: 12) { VStack(alignment: .leading, spacing: 4) { Text("Display mode") .font(.body) Text("Choose what to show in the menu bar (Pace shows usage vs. expected).") .font(.footnote) .foregroundStyle(.tertiary) } Spacer() Picker("Display mode", selection: self.$settings.menuBarDisplayMode) { ForEach(MenuBarDisplayMode.allCases) { mode in Text(mode.label).tag(mode) } } .labelsHidden() .pickerStyle(.menu) .frame(maxWidth: 200) } .disabled(!self.settings.menuBarShowsBrandIconWithPercent) .opacity(self.settings.menuBarShowsBrandIconWithPercent ? 1 : 0.5) } Divider() SettingsSection(contentSpacing: 12) { Text("Menu content") .font(.caption) .foregroundStyle(.secondary) .textCase(.uppercase) PreferenceToggleRow( title: "Show usage as used", subtitle: "Progress bars fill as you consume quota (instead of showing remaining).", binding: self.$settings.usageBarsShowUsed) PreferenceToggleRow( title: "Show reset time as clock", subtitle: "Display reset times as absolute clock values instead of countdowns.", binding: self.$settings.resetTimesShowAbsolute) PreferenceToggleRow( title: "Show credits + extra usage", subtitle: "Show Codex Credits and Claude Extra usage sections in the menu.", binding: self.$settings.showOptionalCreditsAndExtraUsage) PreferenceToggleRow( title: "Show all token accounts", subtitle: "Stack token accounts in the menu (otherwise show an account switcher bar).", binding: self.$settings.showAllTokenAccountsInMenu) self.overviewProviderSelector } } .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 20) .padding(.vertical, 12) .onAppear { self.reconcileOverviewSelection() } .onChange(of: self.settings.mergeIcons) { _, isEnabled in guard isEnabled else { self.isOverviewProviderPopoverPresented = false return } self.reconcileOverviewSelection() } .onChange(of: self.activeProvidersInOrder) { _, _ in if self.activeProvidersInOrder.isEmpty { self.isOverviewProviderPopoverPresented = false } self.reconcileOverviewSelection() } } } private var overviewProviderSelector: some View { VStack(alignment: .leading, spacing: 6) { HStack(alignment: .center, spacing: 12) { Text("Overview tab providers") .font(.body) Spacer(minLength: 0) if self.showsOverviewConfigureButton { Button("Configure…") { self.isOverviewProviderPopoverPresented = true } .offset(y: 1) .popover(isPresented: self.$isOverviewProviderPopoverPresented, arrowEdge: .bottom) { self.overviewProviderPopover } } } if !self.settings.mergeIcons { Text("Enable Merge Icons to configure Overview tab providers.") .font(.footnote) .foregroundStyle(.tertiary) } else if self.activeProvidersInOrder.isEmpty { Text("No enabled providers available for Overview.") .font(.footnote) .foregroundStyle(.tertiary) } else { Text(self.overviewProviderSelectionSummary) .font(.footnote) .foregroundStyle(.tertiary) .lineLimit(2) .truncationMode(.tail) } } } private var overviewProviderPopover: some View { VStack(alignment: .leading, spacing: 10) { Text("Choose up to \(Self.maxOverviewProviders) providers") .font(.headline) Text("Overview rows always follow provider order.") .font(.footnote) .foregroundStyle(.tertiary) ScrollView(.vertical, showsIndicators: true) { VStack(alignment: .leading, spacing: 6) { ForEach(self.activeProvidersInOrder, id: \.self) { provider in Toggle( isOn: Binding( get: { self.overviewSelectedProviders.contains(provider) }, set: { shouldSelect in self.setOverviewProviderSelection(provider: provider, isSelected: shouldSelect) })) { Text(self.providerDisplayName(provider)) .font(.body) } .toggleStyle(.checkbox) .disabled( !self.overviewSelectedProviders.contains(provider) && self.overviewSelectedProviders.count >= Self.maxOverviewProviders) } } } .frame(maxHeight: 220) } .padding(12) .frame(width: 280) } private var activeProvidersInOrder: [UsageProvider] { self.store.enabledProviders() } private var overviewSelectedProviders: [UsageProvider] { self.settings.resolvedMergedOverviewProviders( activeProviders: self.activeProvidersInOrder, maxVisibleProviders: Self.maxOverviewProviders) } private var showsOverviewConfigureButton: Bool { self.settings.mergeIcons && !self.activeProvidersInOrder.isEmpty } private var overviewProviderSelectionSummary: String { let selectedNames = self.overviewSelectedProviders.map(self.providerDisplayName) guard !selectedNames.isEmpty else { return "No providers selected" } return selectedNames.joined(separator: ", ") } private func providerDisplayName(_ provider: UsageProvider) -> String { ProviderDescriptorRegistry.descriptor(for: provider).metadata.displayName } private func setOverviewProviderSelection(provider: UsageProvider, isSelected: Bool) { _ = self.settings.setMergedOverviewProviderSelection( provider: provider, isSelected: isSelected, activeProviders: self.activeProvidersInOrder, maxVisibleProviders: Self.maxOverviewProviders) } private func reconcileOverviewSelection() { _ = self.settings.reconcileMergedOverviewSelectedProviders( activeProviders: self.activeProvidersInOrder, maxVisibleProviders: Self.maxOverviewProviders) }}
import CodexBarCore
import SwiftUI
@MainActor
struct DisplayPane: View {
private static let maxOverviewProviders = SettingsStore.mergedOverviewProviderLimit
@State private var isOverviewProviderPopoverPresented = false
@Bindable var settings: SettingsStore
@Bindable var store: UsageStore
var body: some View {
ScrollView(.vertical, showsIndicators: true) {
VStack(alignment: .leading, spacing: 16) {
SettingsSection(contentSpacing: 12) {
Text("Menu bar")
.font(.caption)
.foregroundStyle(.secondary)
.textCase(.uppercase)
PreferenceToggleRow(
title: "Merge Icons",
subtitle: "Use a single menu bar icon with a provider switcher.",
binding: self.$settings.mergeIcons)
PreferenceToggleRow(
title: "Switcher shows icons",
subtitle: "Show provider icons in the switcher (otherwise show a weekly progress line).",
binding: self.$settings.switcherShowsIcons)
.disabled(!self.settings.mergeIcons)
.opacity(self.settings.mergeIcons ? 1 : 0.5)
PreferenceToggleRow(
title: "Show most-used provider",
subtitle: "Menu bar auto-shows the provider closest to its rate limit.",
binding: self.$settings.menuBarShowsHighestUsage)
.disabled(!self.settings.mergeIcons)
.opacity(self.settings.mergeIcons ? 1 : 0.5)
PreferenceToggleRow(
title: "Menu bar shows percent",
subtitle: "Replace critter bars with provider branding icons and a percentage.",
binding: self.$settings.menuBarShowsBrandIconWithPercent)
HStack(alignment: .top, spacing: 12) {
VStack(alignment: .leading, spacing: 4) {
Text("Display mode")
.font(.body)
Text("Choose what to show in the menu bar (Pace shows usage vs. expected).")
.font(.footnote)
.foregroundStyle(.tertiary)
}
Spacer()
Picker("Display mode", selection: self.$settings.menuBarDisplayMode) {
ForEach(MenuBarDisplayMode.allCases) { mode in
Text(mode.label).tag(mode)
}
}
.labelsHidden()
.pickerStyle(.menu)
.frame(maxWidth: 200)
}
.disabled(!self.settings.menuBarShowsBrandIconWithPercent)
.opacity(self.settings.menuBarShowsBrandIconWithPercent ? 1 : 0.5)
}
Divider()
SettingsSection(contentSpacing: 12) {
Text("Menu content")
.font(.caption)
.foregroundStyle(.secondary)
.textCase(.uppercase)
PreferenceToggleRow(
title: "Show usage as used",
subtitle: "Progress bars fill as you consume quota (instead of showing remaining).",
binding: self.$settings.usageBarsShowUsed)
PreferenceToggleRow(
title: "Show reset time as clock",
subtitle: "Display reset times as absolute clock values instead of countdowns.",
binding: self.$settings.resetTimesShowAbsolute)
PreferenceToggleRow(
title: "Show credits + extra usage",
subtitle: "Show Codex Credits and Claude Extra usage sections in the menu.",
binding: self.$settings.showOptionalCreditsAndExtraUsage)
PreferenceToggleRow(
title: "Show all token accounts",
subtitle: "Stack token accounts in the menu (otherwise show an account switcher bar).",
binding: self.$settings.showAllTokenAccountsInMenu)
self.overviewProviderSelector
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 20)
.padding(.vertical, 12)
.onAppear {
self.reconcileOverviewSelection()
}
.onChange(of: self.settings.mergeIcons) { _, isEnabled in
guard isEnabled else {
self.isOverviewProviderPopoverPresented = false
return
}
self.reconcileOverviewSelection()
}
.onChange(of: self.activeProvidersInOrder) { _, _ in
if self.activeProvidersInOrder.isEmpty {
self.isOverviewProviderPopoverPresented = false
}
self.reconcileOverviewSelection()
}
}
}
private var overviewProviderSelector: some View {
VStack(alignment: .leading, spacing: 6) {
HStack(alignment: .center, spacing: 12) {
Text("Overview tab providers")
.font(.body)
Spacer(minLength: 0)
if self.showsOverviewConfigureButton {
Button("Configure…") {
self.isOverviewProviderPopoverPresented = true
}
.offset(y: 1)
.popover(isPresented: self.$isOverviewProviderPopoverPresented, arrowEdge: .bottom) {
self.overviewProviderPopover
}
}
}
if !self.settings.mergeIcons {
Text("Enable Merge Icons to configure Overview tab providers.")
.font(.footnote)
.foregroundStyle(.tertiary)
} else if self.activeProvidersInOrder.isEmpty {
Text("No enabled providers available for Overview.")
.font(.footnote)
.foregroundStyle(.tertiary)
} else {
Text(self.overviewProviderSelectionSummary)
.font(.footnote)
.foregroundStyle(.tertiary)
.lineLimit(2)
.truncationMode(.tail)
}
}
}
private var overviewProviderPopover: some View {
VStack(alignment: .leading, spacing: 10) {
Text("Choose up to \(Self.maxOverviewProviders) providers")
.font(.headline)
Text("Overview rows always follow provider order.")
.font(.footnote)
.foregroundStyle(.tertiary)
ScrollView(.vertical, showsIndicators: true) {
VStack(alignment: .leading, spacing: 6) {
ForEach(self.activeProvidersInOrder, id: \.self) { provider in
Toggle(
isOn: Binding(
get: { self.overviewSelectedProviders.contains(provider) },
set: { shouldSelect in
self.setOverviewProviderSelection(provider: provider, isSelected: shouldSelect)
})) {
Text(self.providerDisplayName(provider))
.font(.body)
}
.toggleStyle(.checkbox)
.disabled(
!self.overviewSelectedProviders.contains(provider) &&
self.overviewSelectedProviders.count >= Self.maxOverviewProviders)
}
}
}
.frame(maxHeight: 220)
}
.padding(12)
.frame(width: 280)
}
private var activeProvidersInOrder: [UsageProvider] {
self.store.enabledProviders()
}
private var overviewSelectedProviders: [UsageProvider] {
self.settings.resolvedMergedOverviewProviders(
activeProviders: self.activeProvidersInOrder,
maxVisibleProviders: Self.maxOverviewProviders)
}
private var showsOverviewConfigureButton: Bool {
self.settings.mergeIcons && !self.activeProvidersInOrder.isEmpty
}
private var overviewProviderSelectionSummary: String {
let selectedNames = self.overviewSelectedProviders.map(self.providerDisplayName)
guard !selectedNames.isEmpty else { return "No providers selected" }
return selectedNames.joined(separator: ", ")
}
private func providerDisplayName(_ provider: UsageProvider) -> String {
ProviderDescriptorRegistry.descriptor(for: provider).metadata.displayName
}
private func setOverviewProviderSelection(provider: UsageProvider, isSelected: Bool) {
_ = self.settings.setMergedOverviewProviderSelection(
provider: provider,
isSelected: isSelected,
activeProviders: self.activeProvidersInOrder,
maxVisibleProviders: Self.maxOverviewProviders)
}
private func reconcileOverviewSelection() {
_ = self.settings.reconcileMergedOverviewSelectedProviders(
activeProviders: self.activeProvidersInOrder,
maxVisibleProviders: Self.maxOverviewProviders)
}
}