1651 lines · 65.7 KB · Swift
RawDownload
import AppKitimport CodexBarCoreimport Observationimport SwiftUI // MARK: - NSMenu construction extension StatusItemController {    private static let menuCardBaseWidth: CGFloat = 310    private static let menuOpenRefreshDelay: Duration = .seconds(1.2)    private struct OpenAIWebMenuItems {        let hasUsageBreakdown: Bool        let hasCreditsHistory: Bool        let hasCostHistory: Bool    }     private func menuCardWidth(for providers: [UsageProvider], menu: NSMenu? = nil) -> CGFloat {        _ = menu        return Self.menuCardBaseWidth    }     func makeMenu() -> NSMenu {        guard self.shouldMergeIcons else {            return self.makeMenu(for: nil)        }        let menu = NSMenu()        menu.autoenablesItems = false        menu.delegate = self        return menu    }     func menuWillOpen(_ menu: NSMenu) {        if self.isHostedSubviewMenu(menu) {            self.refreshHostedSubviewHeights(in: menu)            self.openMenus[ObjectIdentifier(menu)] = menu            // Removed redundant async refresh - single pass is sufficient after initial layout            return        }         var provider: UsageProvider?        if self.shouldMergeIcons {            self.selectedMenuProvider = self.resolvedMenuProvider()            self.lastMenuProvider = self.selectedMenuProvider ?? .codex            provider = self.selectedMenuProvider        } else {            if let menuProvider = self.menuProviders[ObjectIdentifier(menu)] {                self.lastMenuProvider = menuProvider                provider = menuProvider            } else if menu === self.fallbackMenu {                self.lastMenuProvider = self.store.enabledProviders().first ?? .codex                provider = nil            } else {                let resolved = self.store.enabledProviders().first ?? .codex                self.lastMenuProvider = resolved                provider = resolved            }        }         if self.menuNeedsRefresh(menu) {            self.populateMenu(menu, provider: provider)            self.markMenuFresh(menu)        }        self.refreshMenuCardHeights(in: menu)        self.openMenus[ObjectIdentifier(menu)] = menu        self.scheduleOpenMenuRefresh(for: menu)        // Removed redundant async refresh - single pass is sufficient after initial layout    }     func menuDidClose(_ menu: NSMenu) {        self.openMenus.removeValue(forKey: ObjectIdentifier(menu))        self.menuRefreshTasks.removeValue(forKey: ObjectIdentifier(menu))?.cancel()        for menuItem in menu.items {            (menuItem.view as? MenuCardHighlighting)?.setHighlighted(false)        }    }     func menu(_ menu: NSMenu, willHighlight item: NSMenuItem?) {        for menuItem in menu.items {            let highlighted = menuItem == item && menuItem.isEnabled            (menuItem.view as? MenuCardHighlighting)?.setHighlighted(highlighted)        }    }     private func populateMenu(_ menu: NSMenu, provider: UsageProvider?) {        menu.removeAllItems()         let selectedProvider = provider        let enabledProviders = self.store.enabledProviders()        let menuWidth = self.menuCardWidth(for: enabledProviders, menu: menu)        let descriptor = MenuDescriptor.build(            provider: selectedProvider,            store: self.store,            settings: self.settings,            account: self.account,            updateReady: self.updater.updateStatus.isUpdateReady)        let dashboard = self.store.openAIDashboard        let currentProvider = selectedProvider ?? enabledProviders.first ?? .codex        let openAIWebEligible = currentProvider == .codex &&            self.store.openAIDashboardRequiresLogin == false &&            dashboard != nil        let hasCreditsHistory = openAIWebEligible && !(dashboard?.dailyBreakdown ?? []).isEmpty        let hasUsageBreakdown = openAIWebEligible && !(dashboard?.usageBreakdown ?? []).isEmpty        let hasCostHistory = self.settings.isCostUsageEffectivelyEnabled(for: currentProvider) &&            (self.store.tokenSnapshot(for: currentProvider)?.daily.isEmpty == false)        let hasOpenAIWebMenuItems = hasCreditsHistory || hasUsageBreakdown || hasCostHistory        var addedOpenAIWebItems = false         if self.shouldMergeIcons, enabledProviders.count > 1 {            let switcherItem = self.makeProviderSwitcherItem(                providers: enabledProviders,                selected: selectedProvider,                menu: menu)            menu.addItem(switcherItem)            menu.addItem(.separator())        }         if let model = self.menuCardModel(for: selectedProvider) {            if hasOpenAIWebMenuItems {                let webItems = OpenAIWebMenuItems(                    hasUsageBreakdown: hasUsageBreakdown,                    hasCreditsHistory: hasCreditsHistory,                    hasCostHistory: hasCostHistory)                self.addMenuCardSections(                    to: menu,                    model: model,                    provider: currentProvider,                    width: menuWidth,                    webItems: webItems)                addedOpenAIWebItems = true            } else {                menu.addItem(self.makeMenuCardItem(                    UsageMenuCardView(model: model, width: menuWidth),                    id: "menuCard",                    width: menuWidth))                if currentProvider == .codex, model.creditsText != nil {                    menu.addItem(self.makeBuyCreditsItem())                }                menu.addItem(.separator())            }        }         if hasOpenAIWebMenuItems {            if !addedOpenAIWebItems {                // Only show these when we actually have additional data.                if hasUsageBreakdown {                    _ = self.addUsageBreakdownSubmenu(to: menu)                }                if hasCreditsHistory {                    _ = self.addCreditsHistorySubmenu(to: menu)                }                if hasCostHistory {                    _ = self.addCostHistorySubmenu(to: menu, provider: currentProvider)                }            }            menu.addItem(.separator())        }         let actionableSections = descriptor.sections.filter { section in            section.entries.contains { entry in                if case .action = entry { return true }                return false            }        }        for (index, section) in actionableSections.enumerated() {            for entry in section.entries {                switch entry {                case let .text(text, style):                    let item = NSMenuItem(title: text, action: nil, keyEquivalent: "")                    item.isEnabled = false                    if style == .headline {                        let font = NSFont.systemFont(ofSize: NSFont.systemFontSize, weight: .semibold)                        item.attributedTitle = NSAttributedString(string: text, attributes: [.font: font])                    } else if style == .secondary {                        let font = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize)                        item.attributedTitle = NSAttributedString(                            string: text,                            attributes: [.font: font, .foregroundColor: NSColor.secondaryLabelColor])                    }                    menu.addItem(item)                case let .action(title, action):                    let (selector, represented) = self.selector(for: action)                    let item = NSMenuItem(title: title, action: selector, keyEquivalent: "")                    item.target = self                    item.representedObject = represented                    if let iconName = action.systemImageName,                       let image = NSImage(systemSymbolName: iconName, accessibilityDescription: nil)                    {                        image.isTemplate = true                        image.size = NSSize(width: 16, height: 16)                        item.image = image                    }                    if case let .switchAccount(targetProvider) = action,                       let subtitle = self.switchAccountSubtitle(for: targetProvider)                    {                        item.isEnabled = false                        self.applySubtitle(subtitle, to: item, title: title)                    }                    menu.addItem(item)                case .divider:                    menu.addItem(.separator())                }            }            if index < actionableSections.count - 1 {                menu.addItem(.separator())            }        }    }     func makeMenu(for provider: UsageProvider?) -> NSMenu {        let menu = NSMenu()        menu.autoenablesItems = false        menu.delegate = self        if let provider {            self.menuProviders[ObjectIdentifier(menu)] = provider        }        return menu    }     private func makeProviderSwitcherItem(        providers: [UsageProvider],        selected: UsageProvider?,        menu: NSMenu) -> NSMenuItem    {        let view = ProviderSwitcherView(            providers: providers,            selected: selected,            width: self.menuCardWidth(for: providers, menu: menu),            showsIcons: self.settings.switcherShowsIcons,            iconProvider: { [weak self] provider in                self?.switcherIcon(for: provider) ?? NSImage()            },            weeklyRemainingProvider: { [weak self] provider in                self?.switcherWeeklyRemaining(for: provider)            },            onSelect: { [weak self, weak menu] provider in                guard let self, let menu else { return }                self.selectedMenuProvider = provider                self.lastMenuProvider = provider                self.populateMenu(menu, provider: provider)                self.markMenuFresh(menu)                self.refreshMenuCardHeights(in: menu)                self.applyIcon(phase: nil)            })        let item = NSMenuItem()        item.view = view        item.isEnabled = false        return item    }     private func resolvedMenuProvider() -> UsageProvider? {        let enabled = self.store.enabledProviders()        if enabled.isEmpty { return .codex }        if let selected = self.selectedMenuProvider, enabled.contains(selected) {            return selected        }        return enabled.first    }     private func menuNeedsRefresh(_ menu: NSMenu) -> Bool {        let key = ObjectIdentifier(menu)        return self.menuVersions[key] != self.menuContentVersion    }     private func markMenuFresh(_ menu: NSMenu) {        let key = ObjectIdentifier(menu)        self.menuVersions[key] = self.menuContentVersion    }     func refreshOpenMenusIfNeeded() {        guard !self.openMenus.isEmpty else { return }        for (key, menu) in self.openMenus {            guard key == ObjectIdentifier(menu) else {                self.openMenus.removeValue(forKey: key)                continue            }             if self.isHostedSubviewMenu(menu) {                self.refreshHostedSubviewHeights(in: menu)                continue            }             if self.menuNeedsRefresh(menu) {                let provider = self.menuProvider(for: menu)                self.populateMenu(menu, provider: provider)                self.markMenuFresh(menu)                self.refreshMenuCardHeights(in: menu)            }        }    }     private func menuProvider(for menu: NSMenu) -> UsageProvider? {        if self.shouldMergeIcons {            return self.selectedMenuProvider ?? self.resolvedMenuProvider()        }        if let provider = self.menuProviders[ObjectIdentifier(menu)] {            return provider        }        if menu === self.fallbackMenu {            return nil        }        return self.store.enabledProviders().first ?? .codex    }     private func scheduleOpenMenuRefresh(for menu: NSMenu) {        self.refreshNow()        let key = ObjectIdentifier(menu)        self.menuRefreshTasks[key]?.cancel()        self.menuRefreshTasks[key] = Task { @MainActor [weak self, weak menu] in            guard let self, let menu else { return }            try? await Task.sleep(for: Self.menuOpenRefreshDelay)            guard !Task.isCancelled else { return }            guard self.openMenus[ObjectIdentifier(menu)] != nil else { return }            guard !self.store.isRefreshing else { return }            let provider = self.menuProvider(for: menu) ?? self.resolvedMenuProvider()            let isStale = provider.map { self.store.isStale(provider: $0) } ?? self.store.isStale            let hasSnapshot = provider.map { self.store.snapshot(for: $0) != nil } ?? true            guard isStale || !hasSnapshot else { return }            self.refreshNow()        }    }     private func refreshMenuCardHeights(in menu: NSMenu) {        // Re-measure the menu card height right before display to avoid stale/incorrect sizing when content        // changes (e.g. dashboard error lines causing wrapping).        let cardItems = menu.items.filter { item in            (item.representedObject as? String)?.hasPrefix("menuCard") == true        }        for item in cardItems {            guard let view = item.view else { continue }            let width = self.menuCardWidth(for: self.store.enabledProviders(), menu: menu)            let height = self.menuCardHeight(for: view, width: width)            view.frame = NSRect(                origin: .zero,                size: NSSize(width: width, height: height))        }    }     private func makeMenuCardItem(        _ view: some View,        id: String,        width: CGFloat,        submenu: NSMenu? = nil) -> NSMenuItem    {        let highlightState = MenuCardHighlightState()        let wrapped = MenuCardSectionContainerView(            highlightState: highlightState,            showsSubmenuIndicator: submenu != nil)        {            view        }        let hosting = MenuCardItemHostingView(rootView: wrapped, highlightState: highlightState)        hosting.frame = NSRect(origin: .zero, size: NSSize(width: width, height: 1))        hosting.needsLayout = true        hosting.layoutSubtreeIfNeeded()        // Important: constrain width before asking SwiftUI for the fitting height, otherwise text wrapping        // changes the required height and the menu item becomes visually "squeezed".        let height = self.menuCardHeight(for: hosting, width: width)        hosting.frame = NSRect(origin: .zero, size: NSSize(width: width, height: height))        let item = NSMenuItem()        item.view = hosting        item.isEnabled = true        item.representedObject = id        item.submenu = submenu        if submenu != nil {            item.target = self            item.action = #selector(self.menuCardNoOp(_:))        }        return item    }     private func menuCardHeight(for view: NSView, width: CGFloat) -> CGFloat {        let basePadding: CGFloat = 6        let descenderSafety: CGFloat = 1         // Fast path: use protocol-based measurement when available (avoids layout passes)        if let measured = view as? MenuCardMeasuring {            return max(1, ceil(measured.measuredHeight(width: width) + basePadding + descenderSafety))        }         // Set frame with target width before measuring.        view.frame = NSRect(origin: .zero, size: NSSize(width: width, height: 1))         // Constrain width for accurate wrapping during layout.        let widthConstraint = view.widthAnchor.constraint(equalToConstant: width)        widthConstraint.priority = .required        widthConstraint.isActive = true         // Single layout pass instead of multiple invalidations.        view.layoutSubtreeIfNeeded()        widthConstraint.isActive = false         // Use fittingSize directly - the frame width already constrains the view        let fitted = view.fittingSize         return max(1, ceil(fitted.height + basePadding + descenderSafety))    }     private func addMenuCardSections(        to menu: NSMenu,        model: UsageMenuCardView.Model,        provider: UsageProvider,        width: CGFloat,        webItems: OpenAIWebMenuItems)    {        let hasUsageBlock = !model.metrics.isEmpty || model.placeholder != nil        let hasCredits = model.creditsText != nil        let hasExtraUsage = model.providerCost != nil        let hasCost = model.tokenUsage != nil        let bottomPadding = CGFloat(hasCredits ? 4 : 6)        let sectionSpacing = CGFloat(6)        let usageBottomPadding = bottomPadding        let creditsBottomPadding = bottomPadding         let headerView = UsageMenuCardHeaderSectionView(            model: model,            showDivider: hasUsageBlock,            width: width)        menu.addItem(self.makeMenuCardItem(headerView, id: "menuCardHeader", width: width))         if hasUsageBlock {            let usageView = UsageMenuCardUsageSectionView(                model: model,                showBottomDivider: false,                bottomPadding: usageBottomPadding,                width: width)            let usageSubmenu = self.makeUsageSubmenu(                provider: provider,                snapshot: self.store.snapshot(for: provider),                webItems: webItems)            menu.addItem(self.makeMenuCardItem(                usageView,                id: "menuCardUsage",                width: width,                submenu: usageSubmenu))        }         if hasCredits || hasExtraUsage || hasCost {            menu.addItem(.separator())        }         if hasCredits {            if hasExtraUsage || hasCost {                menu.addItem(.separator())            }            let creditsView = UsageMenuCardCreditsSectionView(                model: model,                showBottomDivider: false,                topPadding: sectionSpacing,                bottomPadding: creditsBottomPadding,                width: width)            let creditsSubmenu = webItems.hasCreditsHistory ? self.makeCreditsHistorySubmenu() : nil            menu.addItem(self.makeMenuCardItem(                creditsView,                id: "menuCardCredits",                width: width,                submenu: creditsSubmenu))            if provider == .codex {                menu.addItem(self.makeBuyCreditsItem())            }        }        if hasExtraUsage {            if hasCredits {                menu.addItem(.separator())            }            let extraUsageView = UsageMenuCardExtraUsageSectionView(                model: model,                topPadding: sectionSpacing,                bottomPadding: bottomPadding,                width: width)            menu.addItem(self.makeMenuCardItem(                extraUsageView,                id: "menuCardExtraUsage",                width: width))        }        if hasCost {            if hasCredits || hasExtraUsage {                menu.addItem(.separator())            }            let costView = UsageMenuCardCostSectionView(                model: model,                topPadding: sectionSpacing,                bottomPadding: bottomPadding,                width: width)            let costSubmenu = webItems.hasCostHistory ? self.makeCostHistorySubmenu(provider: provider) : nil            menu.addItem(self.makeMenuCardItem(                costView,                id: "menuCardCost",                width: width,                submenu: costSubmenu))        }    }     private func switcherIcon(for provider: UsageProvider) -> NSImage {        if let brand = ProviderBrandIcon.image(for: provider) {            return brand        }         // Fallback to the dynamic icon renderer if resources are missing (e.g. dev bundle mismatch).        let snapshot = self.store.snapshot(for: provider)        let showUsed = self.settings.usageBarsShowUsed

Lines 1–500 of 1651.