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) } }