149 lines
5.6 KiB
Swift
149 lines
5.6 KiB
Swift
import AppKit
|
|
import CodexBarCore
|
|
|
|
extension StatusItemController {
|
|
private static let measuredStandardMenuWidthCacheLimit = 96
|
|
|
|
func menuCardWidth(
|
|
for providers: [UsageProvider],
|
|
selectedProvider: UsageProvider?,
|
|
descriptor: MenuDescriptor) -> CGFloat
|
|
{
|
|
let sectionSets: [[MenuDescriptor.Section]] = if self.shouldMergeIcons, providers.count > 1 {
|
|
providers.map { provider in
|
|
if provider == selectedProvider {
|
|
return descriptor.sections
|
|
}
|
|
return self.makeMenuDescriptor(
|
|
provider: provider,
|
|
includeContextualActions: true).sections
|
|
}
|
|
} else {
|
|
[descriptor.sections]
|
|
}
|
|
return self.measuredMenuCardWidth(for: sectionSets)
|
|
}
|
|
|
|
func measuredMenuCardWidth(for sectionSets: [[MenuDescriptor.Section]]) -> CGFloat {
|
|
let baselineWidth = Self.menuCardBaseWidth
|
|
return sectionSets.reduce(baselineWidth) { width, sections in
|
|
max(width, self.measuredStandardMenuWidth(for: sections, baseWidth: baselineWidth))
|
|
}
|
|
}
|
|
|
|
func makeMenuDescriptor(
|
|
provider: UsageProvider?,
|
|
includeContextualActions: Bool) -> MenuDescriptor
|
|
{
|
|
MenuDescriptor.build(
|
|
provider: provider,
|
|
store: self.store,
|
|
settings: self.settings,
|
|
account: self.account,
|
|
managedCodexAccountCoordinator: self.managedCodexAccountCoordinator,
|
|
codexAccountPromotionCoordinator: self.codexAccountPromotionCoordinator,
|
|
updateReady: self.updater.updateStatus.isUpdateReady,
|
|
includeContextualActions: includeContextualActions,
|
|
agentSessionsEnabled: self.settings.agentSessionsEnabled,
|
|
localAgentSessions: self.agentSessions.localSessions,
|
|
remoteAgentHosts: self.agentSessions.remoteHosts)
|
|
}
|
|
|
|
func measuredStandardMenuWidth(for sections: [MenuDescriptor.Section], baseWidth: CGFloat) -> CGFloat {
|
|
let cacheKey = self.measuredStandardMenuWidthCacheKey(for: sections, baseWidth: baseWidth)
|
|
if let cached = self.measuredStandardMenuWidthCache[cacheKey] {
|
|
return cached
|
|
}
|
|
|
|
let measuringMenu = NSMenu()
|
|
measuringMenu.autoenablesItems = false
|
|
self.addActionableSections(sections, to: measuringMenu, width: baseWidth)
|
|
let measured = ceil(measuringMenu.size.width)
|
|
if self.measuredStandardMenuWidthCache.count >= Self.measuredStandardMenuWidthCacheLimit {
|
|
self.measuredStandardMenuWidthCache.removeAll(keepingCapacity: true)
|
|
}
|
|
self.measuredStandardMenuWidthCache[cacheKey] = measured
|
|
return measured
|
|
}
|
|
|
|
private func measuredStandardMenuWidthCacheKey(
|
|
for sections: [MenuDescriptor.Section],
|
|
baseWidth: CGFloat) -> String
|
|
{
|
|
var parts = [
|
|
"base=\(Int((baseWidth * 100).rounded()))",
|
|
"font=\(Self.menuCardHeightTextScaleToken())",
|
|
self.menuLocalizationSignature(),
|
|
]
|
|
for section in sections {
|
|
parts.append("[")
|
|
for entry in section.entries {
|
|
parts.append(self.measuredStandardMenuWidthCacheToken(for: entry))
|
|
}
|
|
parts.append("]")
|
|
}
|
|
return parts.joined(separator: "\u{1f}")
|
|
}
|
|
|
|
private func measuredStandardMenuWidthCacheToken(for entry: MenuDescriptor.Entry) -> String {
|
|
switch entry {
|
|
case let .text(text, style):
|
|
"text:\(style):\(text)"
|
|
case let .action(title, action):
|
|
"action:\(title):\(self.measuredStandardMenuWidthCacheToken(for: action))"
|
|
case let .unavailable(title, tooltip):
|
|
"unavailable:\(title):\(tooltip ?? "")"
|
|
case let .submenu(title, systemImageName, submenuItems):
|
|
"submenu:\(title):\(systemImageName ?? ""):" + submenuItems.map { item in
|
|
[
|
|
item.title,
|
|
item.isEnabled ? "1" : "0",
|
|
item.isChecked ? "1" : "0",
|
|
item.action.map(self.measuredStandardMenuWidthCacheToken(for:)) ?? "",
|
|
].joined(separator: ":")
|
|
}.joined(separator: ",")
|
|
case .divider:
|
|
"divider"
|
|
}
|
|
}
|
|
|
|
private func measuredStandardMenuWidthCacheToken(for action: MenuDescriptor.MenuAction) -> String {
|
|
switch action {
|
|
case .installUpdate:
|
|
"installUpdate"
|
|
case .refresh:
|
|
"refresh"
|
|
case .refreshAugmentSession:
|
|
"refreshAugmentSession"
|
|
case .dashboard:
|
|
"dashboard"
|
|
case .statusPage:
|
|
"statusPage"
|
|
case .changelog:
|
|
"changelog"
|
|
case .addCodexAccount:
|
|
"addCodexAccount:\(self.codexAddAccountSubtitle() ?? "")"
|
|
case let .requestCodexSystemPromotion(id):
|
|
"requestCodexSystemPromotion:\(id)"
|
|
case let .addProviderAccount(provider):
|
|
"addProviderAccount:\(provider.rawValue)"
|
|
case let .switchAccount(provider):
|
|
"switchAccount:\(provider.rawValue):\(self.switchAccountSubtitle(for: provider) ?? "")"
|
|
case let .openTerminal(command):
|
|
"openTerminal:\(command)"
|
|
case let .loginToProvider(url):
|
|
"loginToProvider:\(url)"
|
|
case .settings:
|
|
"settings"
|
|
case .about:
|
|
"about"
|
|
case .quit:
|
|
"quit"
|
|
case let .copyError(message):
|
|
"copyError:\(message)"
|
|
case let .focusAgentSession(session, remoteHost):
|
|
"focusAgentSession:\(remoteHost ?? "local"):\(session.id)"
|
|
}
|
|
}
|
|
}
|