import AppKitimport CodexBarCoreimport SwiftUI /// SwiftUI card used inside the NSMenu to mirror Apple's rich menu panels.struct UsageMenuCardView: View { struct Model { enum PercentStyle: String, Sendable { case left case used var labelSuffix: String { switch self { case .left: "left" case .used: "used" } } var accessibilityLabel: String { switch self { case .left: "Usage remaining" case .used: "Usage used" } } } struct Metric: Identifiable { let id: String let title: String let percent: Double let percentStyle: PercentStyle let resetText: String? let detailText: String? var percentLabel: String { String(format: "%.0f%% %@", self.percent, self.percentStyle.labelSuffix) } } enum SubtitleStyle { case info case loading case error } struct TokenUsageSection: Sendable { let sessionLine: String let monthLine: String let hintLine: String? let errorLine: String? let errorCopyText: String? } struct ProviderCostSection: Sendable { let title: String let percentUsed: Double let spendLine: String } let providerName: String let email: String let subtitleText: String let subtitleStyle: SubtitleStyle let planText: String? let metrics: [Metric] let creditsText: String? let creditsRemaining: Double? let creditsHintText: String? let creditsHintCopyText: String? let providerCost: ProviderCostSection? let tokenUsage: TokenUsageSection? let placeholder: String? let progressColor: Color } let model: Model let width: CGFloat @Environment(\.menuItemHighlighted) private var isHighlighted init(model: Model, width: CGFloat) { self.model = model self.width = width } var body: some View { VStack(alignment: .leading, spacing: 6) { UsageMenuCardHeaderView(model: self.model) if self.hasDetails { Divider() } if self.model.metrics.isEmpty { if let placeholder = self.model.placeholder { Text(placeholder) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .font(.subheadline) } } else { let hasUsage = !self.model.metrics.isEmpty let hasCredits = self.model.creditsText != nil let hasProviderCost = self.model.providerCost != nil let hasCost = self.model.tokenUsage != nil || hasProviderCost VStack(alignment: .leading, spacing: 12) { if hasUsage { VStack(alignment: .leading, spacing: 12) { ForEach(self.model.metrics) { metric in VStack(alignment: .leading, spacing: 6) { Text(metric.title) .font(.body) .fontWeight(.medium) UsageProgressBar( percent: metric.percent, tint: self.model.progressColor, accessibilityLabel: metric.percentStyle.accessibilityLabel) HStack(alignment: .firstTextBaseline) { Text(metric.percentLabel) .font(.footnote) Spacer() if let reset = metric.resetText { Text(reset) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) } } if let detail = metric.detailText { Text(detail) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .lineLimit(1) } } } } } if hasUsage, hasCredits || hasCost { Divider() } if let credits = self.model.creditsText { CreditsBarContent( creditsText: credits, creditsRemaining: self.model.creditsRemaining, hintText: self.model.creditsHintText, hintCopyText: self.model.creditsHintCopyText, progressColor: self.model.progressColor) } if hasCredits, hasCost { Divider() } if let providerCost = self.model.providerCost { ProviderCostContent( section: providerCost, progressColor: self.model.progressColor) } if hasProviderCost, self.model.tokenUsage != nil { Divider() } if let tokenUsage = self.model.tokenUsage { VStack(alignment: .leading, spacing: 6) { Text("Cost") .font(.body) .fontWeight(.medium) Text(tokenUsage.sessionLine) .font(.footnote) Text(tokenUsage.monthLine) .font(.footnote) if let hint = tokenUsage.hintLine, !hint.isEmpty { Text(hint) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .lineLimit(4) .fixedSize(horizontal: false, vertical: true) } if let error = tokenUsage.errorLine, !error.isEmpty { Text(error) .font(.footnote) .foregroundStyle(MenuHighlightStyle.error(self.isHighlighted)) .lineLimit(4) .fixedSize(horizontal: false, vertical: true) .overlay { ClickToCopyOverlay(copyText: tokenUsage.errorCopyText ?? error) } } } } } .padding(.bottom, self.model.creditsText == nil ? 6 : 0) } } .padding(.horizontal, 16) .padding(.top, 2) .padding(.bottom, 2) .frame(width: self.width, alignment: .leading) } private var hasDetails: Bool { !self.model.metrics.isEmpty || self.model.placeholder != nil || self.model.tokenUsage != nil || self.model.providerCost != nil }} private struct UsageMenuCardHeaderView: View { let model: UsageMenuCardView.Model @Environment(\.menuItemHighlighted) private var isHighlighted var body: some View { VStack(alignment: .leading, spacing: 3) { HStack(alignment: .firstTextBaseline) { Text(self.model.providerName) .font(.headline) .fontWeight(.semibold) Spacer() Text(self.model.email) .font(.subheadline) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) } if self.model.subtitleStyle == .error { VStack(alignment: .leading, spacing: 4) { Text(self.model.subtitleText) .font(.footnote) .foregroundStyle(self.subtitleColor) .lineLimit(6) .multilineTextAlignment(.leading) .fixedSize(horizontal: false, vertical: true) .frame(maxWidth: .infinity, alignment: .leading) HStack(alignment: .firstTextBaseline) { if !self.model.subtitleText.isEmpty { CopyIconButton(copyText: self.model.subtitleText, isHighlighted: self.isHighlighted) } Spacer() if let plan = self.model.planText { Text(plan) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .lineLimit(1) } } } } else { HStack(alignment: .firstTextBaseline) { Text(self.model.subtitleText) .font(.footnote) .foregroundStyle(self.subtitleColor) .lineLimit(1) .multilineTextAlignment(.leading) .fixedSize(horizontal: false, vertical: true) .layoutPriority(1) Spacer() if let plan = self.model.planText { Text(plan) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .lineLimit(1) } } } } } private var subtitleColor: Color { switch self.model.subtitleStyle { case .info: MenuHighlightStyle.secondary(self.isHighlighted) case .loading: MenuHighlightStyle.secondary(self.isHighlighted) case .error: MenuHighlightStyle.error(self.isHighlighted) } }} private struct CopyIconButtonStyle: ButtonStyle { let isHighlighted: Bool func makeBody(configuration: Configuration) -> some View { configuration.label .padding(4) .background { RoundedRectangle(cornerRadius: 4, style: .continuous) .fill(MenuHighlightStyle.secondary(self.isHighlighted).opacity(configuration.isPressed ? 0.18 : 0)) } .scaleEffect(configuration.isPressed ? 0.94 : 1) .animation(.easeOut(duration: 0.12), value: configuration.isPressed) }} private struct CopyIconButton: View { let copyText: String let isHighlighted: Bool @State private var didCopy = false @State private var resetTask: Task<Void, Never>? var body: some View { Button { self.copyToPasteboard() withAnimation(.easeOut(duration: 0.12)) { self.didCopy = true } self.resetTask?.cancel() self.resetTask = Task { @MainActor in try? await Task.sleep(for: .seconds(0.9)) withAnimation(.easeOut(duration: 0.2)) { self.didCopy = false } } } label: { Image(systemName: self.didCopy ? "checkmark" : "doc.on.doc") .font(.caption2.weight(.semibold)) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .frame(width: 18, height: 18) } .buttonStyle(CopyIconButtonStyle(isHighlighted: self.isHighlighted)) .accessibilityLabel(self.didCopy ? "Copied" : "Copy error") } private func copyToPasteboard() { let pb = NSPasteboard.general pb.clearContents() pb.setString(self.copyText, forType: .string) }} private struct ProviderCostContent: View { let section: UsageMenuCardView.Model.ProviderCostSection let progressColor: Color @Environment(\.menuItemHighlighted) private var isHighlighted var body: some View { VStack(alignment: .leading, spacing: 6) { Text(self.section.title) .font(.body) .fontWeight(.medium) UsageProgressBar( percent: self.section.percentUsed, tint: self.progressColor, accessibilityLabel: "Extra usage spent") HStack(alignment: .firstTextBaseline) { Text(self.section.spendLine) .font(.footnote) Spacer() Text(String(format: "%.0f%% used", min(100, max(0, self.section.percentUsed)))) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) } } }} struct UsageMenuCardHeaderSectionView: View { let model: UsageMenuCardView.Model let showDivider: Bool let width: CGFloat var body: some View { VStack(alignment: .leading, spacing: 6) { UsageMenuCardHeaderView(model: self.model) if self.showDivider { Divider() } } .padding(.horizontal, 16) .padding(.top, 2) .padding(.bottom, self.model.subtitleStyle == .error ? 2 : 0) .frame(width: self.width, alignment: .leading) }} struct UsageMenuCardUsageSectionView: View { let model: UsageMenuCardView.Model let showBottomDivider: Bool let bottomPadding: CGFloat let width: CGFloat @Environment(\.menuItemHighlighted) private var isHighlighted var body: some View { VStack(alignment: .leading, spacing: 12) { if self.model.metrics.isEmpty { if let placeholder = self.model.placeholder { Text(placeholder) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .font(.subheadline) } } else { ForEach(self.model.metrics) { metric in VStack(alignment: .leading, spacing: 6) { Text(metric.title) .font(.body) .fontWeight(.medium) UsageProgressBar( percent: metric.percent, tint: self.model.progressColor, accessibilityLabel: metric.percentStyle.accessibilityLabel) HStack(alignment: .firstTextBaseline) { Text(metric.percentLabel) .font(.footnote) Spacer() if let reset = metric.resetText { Text(reset) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) } } if let detail = metric.detailText { Text(detail) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .lineLimit(1) } } } } if self.showBottomDivider { Divider() } } .padding(.horizontal, 16) .padding(.top, 10) .padding(.bottom, self.bottomPadding) .frame(width: self.width, alignment: .leading) }} struct UsageMenuCardCreditsSectionView: View { let model: UsageMenuCardView.Model let showBottomDivider: Bool let topPadding: CGFloat let bottomPadding: CGFloat let width: CGFloat init( model: UsageMenuCardView.Model, showBottomDivider: Bool, topPadding: CGFloat, bottomPadding: CGFloat, width: CGFloat) { self.model = model self.showBottomDivider = showBottomDivider self.topPadding = topPadding self.bottomPadding = bottomPadding self.width = width } var body: some View { if let credits = self.model.creditsText { VStack(alignment: .leading, spacing: 6) { CreditsBarContent( creditsText: credits, creditsRemaining: self.model.creditsRemaining, hintText: self.model.creditsHintText, hintCopyText: self.model.creditsHintCopyText, progressColor: self.model.progressColor) if self.showBottomDivider { Divider() } } .padding(.horizontal, 16) .padding(.top, self.topPadding) .padding(.bottom, self.bottomPadding) .frame(width: self.width, alignment: .leading) } }} private struct CreditsBarContent: View { private static let fullScaleTokens: Double = 1000 let creditsText: String let creditsRemaining: Double? let hintText: String? let hintCopyText: String? let progressColor: Color @Environment(\.menuItemHighlighted) private var isHighlighted private var percentLeft: Double? { guard let creditsRemaining else { return nil } let percent = (creditsRemaining / Self.fullScaleTokens) * 100 return min(100, max(0, percent)) } private var scaleText: String { let scale = UsageFormatter.tokenCountString(Int(Self.fullScaleTokens)) return "\(scale) tokens" } var body: some View { VStack(alignment: .leading, spacing: 6) { Text("Credits") .font(.body) .fontWeight(.medium) if let percentLeft { UsageProgressBar( percent: percentLeft, tint: self.progressColor, accessibilityLabel: "Credits remaining") HStack(alignment: .firstTextBaseline) { Text(self.creditsText) .font(.caption) Spacer() Text(self.scaleText)Lines 1–500 of 925. Load more
import AppKit import CodexBarCore import SwiftUI /// SwiftUI card used inside the NSMenu to mirror Apple's rich menu panels. struct UsageMenuCardView: View { struct Model { enum PercentStyle: String, Sendable { case left case used var labelSuffix: String { switch self { case .left: "left" case .used: "used" } } var accessibilityLabel: String { switch self { case .left: "Usage remaining" case .used: "Usage used" } } } struct Metric: Identifiable { let id: String let title: String let percent: Double let percentStyle: PercentStyle let resetText: String? let detailText: String? var percentLabel: String { String(format: "%.0f%% %@", self.percent, self.percentStyle.labelSuffix) } } enum SubtitleStyle { case info case loading case error } struct TokenUsageSection: Sendable { let sessionLine: String let monthLine: String let hintLine: String? let errorLine: String? let errorCopyText: String? } struct ProviderCostSection: Sendable { let title: String let percentUsed: Double let spendLine: String } let providerName: String let email: String let subtitleText: String let subtitleStyle: SubtitleStyle let planText: String? let metrics: [Metric] let creditsText: String? let creditsRemaining: Double? let creditsHintText: String? let creditsHintCopyText: String? let providerCost: ProviderCostSection? let tokenUsage: TokenUsageSection? let placeholder: String? let progressColor: Color } let model: Model let width: CGFloat @Environment(\.menuItemHighlighted) private var isHighlighted init(model: Model, width: CGFloat) { self.model = model self.width = width } var body: some View { VStack(alignment: .leading, spacing: 6) { UsageMenuCardHeaderView(model: self.model) if self.hasDetails { Divider() } if self.model.metrics.isEmpty { if let placeholder = self.model.placeholder { Text(placeholder) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .font(.subheadline) } } else { let hasUsage = !self.model.metrics.isEmpty let hasCredits = self.model.creditsText != nil let hasProviderCost = self.model.providerCost != nil let hasCost = self.model.tokenUsage != nil || hasProviderCost VStack(alignment: .leading, spacing: 12) { if hasUsage { VStack(alignment: .leading, spacing: 12) { ForEach(self.model.metrics) { metric in VStack(alignment: .leading, spacing: 6) { Text(metric.title) .font(.body) .fontWeight(.medium) UsageProgressBar( percent: metric.percent, tint: self.model.progressColor, accessibilityLabel: metric.percentStyle.accessibilityLabel) HStack(alignment: .firstTextBaseline) { Text(metric.percentLabel) .font(.footnote) Spacer() if let reset = metric.resetText { Text(reset) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) } } if let detail = metric.detailText { Text(detail) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .lineLimit(1) } } } } } if hasUsage, hasCredits || hasCost { Divider() } if let credits = self.model.creditsText { CreditsBarContent( creditsText: credits, creditsRemaining: self.model.creditsRemaining, hintText: self.model.creditsHintText, hintCopyText: self.model.creditsHintCopyText, progressColor: self.model.progressColor) } if hasCredits, hasCost { Divider() } if let providerCost = self.model.providerCost { ProviderCostContent( section: providerCost, progressColor: self.model.progressColor) } if hasProviderCost, self.model.tokenUsage != nil { Divider() } if let tokenUsage = self.model.tokenUsage { VStack(alignment: .leading, spacing: 6) { Text("Cost") .font(.body) .fontWeight(.medium) Text(tokenUsage.sessionLine) .font(.footnote) Text(tokenUsage.monthLine) .font(.footnote) if let hint = tokenUsage.hintLine, !hint.isEmpty { Text(hint) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .lineLimit(4) .fixedSize(horizontal: false, vertical: true) } if let error = tokenUsage.errorLine, !error.isEmpty { Text(error) .font(.footnote) .foregroundStyle(MenuHighlightStyle.error(self.isHighlighted)) .lineLimit(4) .fixedSize(horizontal: false, vertical: true) .overlay { ClickToCopyOverlay(copyText: tokenUsage.errorCopyText ?? error) } } } } } .padding(.bottom, self.model.creditsText == nil ? 6 : 0) } } .padding(.horizontal, 16) .padding(.top, 2) .padding(.bottom, 2) .frame(width: self.width, alignment: .leading) } private var hasDetails: Bool { !self.model.metrics.isEmpty || self.model.placeholder != nil || self.model.tokenUsage != nil || self.model.providerCost != nil } } private struct UsageMenuCardHeaderView: View { let model: UsageMenuCardView.Model @Environment(\.menuItemHighlighted) private var isHighlighted var body: some View { VStack(alignment: .leading, spacing: 3) { HStack(alignment: .firstTextBaseline) { Text(self.model.providerName) .font(.headline) .fontWeight(.semibold) Spacer() Text(self.model.email) .font(.subheadline) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) } if self.model.subtitleStyle == .error { VStack(alignment: .leading, spacing: 4) { Text(self.model.subtitleText) .font(.footnote) .foregroundStyle(self.subtitleColor) .lineLimit(6) .multilineTextAlignment(.leading) .fixedSize(horizontal: false, vertical: true) .frame(maxWidth: .infinity, alignment: .leading) HStack(alignment: .firstTextBaseline) { if !self.model.subtitleText.isEmpty { CopyIconButton(copyText: self.model.subtitleText, isHighlighted: self.isHighlighted) } Spacer() if let plan = self.model.planText { Text(plan) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .lineLimit(1) } } } } else { HStack(alignment: .firstTextBaseline) { Text(self.model.subtitleText) .font(.footnote) .foregroundStyle(self.subtitleColor) .lineLimit(1) .multilineTextAlignment(.leading) .fixedSize(horizontal: false, vertical: true) .layoutPriority(1) Spacer() if let plan = self.model.planText { Text(plan) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .lineLimit(1) } } } } } private var subtitleColor: Color { switch self.model.subtitleStyle { case .info: MenuHighlightStyle.secondary(self.isHighlighted) case .loading: MenuHighlightStyle.secondary(self.isHighlighted) case .error: MenuHighlightStyle.error(self.isHighlighted) } } } private struct CopyIconButtonStyle: ButtonStyle { let isHighlighted: Bool func makeBody(configuration: Configuration) -> some View { configuration.label .padding(4) .background { RoundedRectangle(cornerRadius: 4, style: .continuous) .fill(MenuHighlightStyle.secondary(self.isHighlighted).opacity(configuration.isPressed ? 0.18 : 0)) } .scaleEffect(configuration.isPressed ? 0.94 : 1) .animation(.easeOut(duration: 0.12), value: configuration.isPressed) } } private struct CopyIconButton: View { let copyText: String let isHighlighted: Bool @State private var didCopy = false @State private var resetTask: Task<Void, Never>? var body: some View { Button { self.copyToPasteboard() withAnimation(.easeOut(duration: 0.12)) { self.didCopy = true } self.resetTask?.cancel() self.resetTask = Task { @MainActor in try? await Task.sleep(for: .seconds(0.9)) withAnimation(.easeOut(duration: 0.2)) { self.didCopy = false } } } label: { Image(systemName: self.didCopy ? "checkmark" : "doc.on.doc") .font(.caption2.weight(.semibold)) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .frame(width: 18, height: 18) } .buttonStyle(CopyIconButtonStyle(isHighlighted: self.isHighlighted)) .accessibilityLabel(self.didCopy ? "Copied" : "Copy error") } private func copyToPasteboard() { let pb = NSPasteboard.general pb.clearContents() pb.setString(self.copyText, forType: .string) } } private struct ProviderCostContent: View { let section: UsageMenuCardView.Model.ProviderCostSection let progressColor: Color @Environment(\.menuItemHighlighted) private var isHighlighted var body: some View { VStack(alignment: .leading, spacing: 6) { Text(self.section.title) .font(.body) .fontWeight(.medium) UsageProgressBar( percent: self.section.percentUsed, tint: self.progressColor, accessibilityLabel: "Extra usage spent") HStack(alignment: .firstTextBaseline) { Text(self.section.spendLine) .font(.footnote) Spacer() Text(String(format: "%.0f%% used", min(100, max(0, self.section.percentUsed)))) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) } } } } struct UsageMenuCardHeaderSectionView: View { let model: UsageMenuCardView.Model let showDivider: Bool let width: CGFloat var body: some View { VStack(alignment: .leading, spacing: 6) { UsageMenuCardHeaderView(model: self.model) if self.showDivider { Divider() } } .padding(.horizontal, 16) .padding(.top, 2) .padding(.bottom, self.model.subtitleStyle == .error ? 2 : 0) .frame(width: self.width, alignment: .leading) } } struct UsageMenuCardUsageSectionView: View { let model: UsageMenuCardView.Model let showBottomDivider: Bool let bottomPadding: CGFloat let width: CGFloat @Environment(\.menuItemHighlighted) private var isHighlighted var body: some View { VStack(alignment: .leading, spacing: 12) { if self.model.metrics.isEmpty { if let placeholder = self.model.placeholder { Text(placeholder) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .font(.subheadline) } } else { ForEach(self.model.metrics) { metric in VStack(alignment: .leading, spacing: 6) { Text(metric.title) .font(.body) .fontWeight(.medium) UsageProgressBar( percent: metric.percent, tint: self.model.progressColor, accessibilityLabel: metric.percentStyle.accessibilityLabel) HStack(alignment: .firstTextBaseline) { Text(metric.percentLabel) .font(.footnote) Spacer() if let reset = metric.resetText { Text(reset) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) } } if let detail = metric.detailText { Text(detail) .font(.footnote) .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) .lineLimit(1) } } } } if self.showBottomDivider { Divider() } } .padding(.horizontal, 16) .padding(.top, 10) .padding(.bottom, self.bottomPadding) .frame(width: self.width, alignment: .leading) } } struct UsageMenuCardCreditsSectionView: View { let model: UsageMenuCardView.Model let showBottomDivider: Bool let topPadding: CGFloat let bottomPadding: CGFloat let width: CGFloat init( model: UsageMenuCardView.Model, showBottomDivider: Bool, topPadding: CGFloat, bottomPadding: CGFloat, width: CGFloat) { self.model = model self.showBottomDivider = showBottomDivider self.topPadding = topPadding self.bottomPadding = bottomPadding self.width = width } var body: some View { if let credits = self.model.creditsText { VStack(alignment: .leading, spacing: 6) { CreditsBarContent( creditsText: credits, creditsRemaining: self.model.creditsRemaining, hintText: self.model.creditsHintText, hintCopyText: self.model.creditsHintCopyText, progressColor: self.model.progressColor) if self.showBottomDivider { Divider() } } .padding(.horizontal, 16) .padding(.top, self.topPadding) .padding(.bottom, self.bottomPadding) .frame(width: self.width, alignment: .leading) } } } private struct CreditsBarContent: View { private static let fullScaleTokens: Double = 1000 let creditsText: String let creditsRemaining: Double? let hintText: String? let hintCopyText: String? let progressColor: Color @Environment(\.menuItemHighlighted) private var isHighlighted private var percentLeft: Double? { guard let creditsRemaining else { return nil } let percent = (creditsRemaining / Self.fullScaleTokens) * 100 return min(100, max(0, percent)) } private var scaleText: String { let scale = UsageFormatter.tokenCountString(Int(Self.fullScaleTokens)) return "\(scale) tokens" } var body: some View { VStack(alignment: .leading, spacing: 6) { Text("Credits") .font(.body) .fontWeight(.medium) if let percentLeft { UsageProgressBar( percent: percentLeft, tint: self.progressColor, accessibilityLabel: "Credits remaining") HStack(alignment: .firstTextBaseline) { Text(self.creditsText) .font(.caption) Spacer() Text(self.scaleText)