143 lines
5.4 KiB
Swift
143 lines
5.4 KiB
Swift
import AppKit
|
|
|
|
struct CachedMergedSwitcherMenuContent {
|
|
let requiredMenuContentVersion: Int
|
|
let menuWidth: CGFloat
|
|
let codexAccountDisplay: CodexAccountMenuDisplay?
|
|
let tokenAccountDisplay: TokenAccountMenuDisplay?
|
|
let localizationSignature: String
|
|
let items: [NSMenuItem]
|
|
|
|
func matches(
|
|
requiredMenuContentVersion: Int,
|
|
menuWidth: CGFloat,
|
|
codexAccountDisplay: CodexAccountMenuDisplay?,
|
|
tokenAccountDisplay: TokenAccountMenuDisplay?,
|
|
localizationSignature: String)
|
|
-> Bool
|
|
{
|
|
self.requiredMenuContentVersion >= requiredMenuContentVersion &&
|
|
abs(self.menuWidth - menuWidth) <= 0.5 &&
|
|
self.codexAccountDisplay == codexAccountDisplay &&
|
|
self.tokenAccountDisplay == tokenAccountDisplay &&
|
|
self.localizationSignature == localizationSignature
|
|
}
|
|
}
|
|
|
|
struct MergedSwitcherContentCacheContext {
|
|
let menuWidth: CGFloat
|
|
let codexAccountDisplay: CodexAccountMenuDisplay?
|
|
let tokenAccountDisplay: TokenAccountMenuDisplay?
|
|
let contentVersion: Int?
|
|
}
|
|
|
|
extension StatusItemController {
|
|
func preservingMergedSwitcherContentCachesDuringInvalidation(_ body: () -> Void) {
|
|
let previous = self.preservesMergedSwitcherContentCachesDuringInvalidation
|
|
self.preservesMergedSwitcherContentCachesDuringInvalidation = true
|
|
defer { self.preservesMergedSwitcherContentCachesDuringInvalidation = previous }
|
|
body()
|
|
}
|
|
|
|
func clearMergedSwitcherContentCaches() {
|
|
self.mergedSwitcherContentCaches.removeAll(keepingCapacity: true)
|
|
}
|
|
|
|
func clearMergedSwitcherContentCache(for menu: NSMenu) {
|
|
self.mergedSwitcherContentCaches.removeValue(forKey: ObjectIdentifier(menu))
|
|
}
|
|
|
|
func cacheVisibleMergedSwitcherContent(
|
|
in menu: NSMenu,
|
|
selection: ProviderSwitcherSelection,
|
|
contentStartIndex: Int,
|
|
menuWidth: CGFloat,
|
|
contentVersion: Int? = nil)
|
|
{
|
|
guard self.shouldMergeIcons else { return }
|
|
guard menu.items.first?.view is ProviderSwitcherView else { return }
|
|
guard contentStartIndex < menu.items.count else { return }
|
|
let items = Array(menu.items[contentStartIndex...])
|
|
self.cacheMergedSwitcherContent(
|
|
items,
|
|
in: menu,
|
|
selection: selection,
|
|
context: MergedSwitcherContentCacheContext(
|
|
menuWidth: menuWidth,
|
|
codexAccountDisplay: self.lastCodexAccountMenuDisplay,
|
|
tokenAccountDisplay: self.lastTokenAccountMenuDisplay,
|
|
contentVersion: contentVersion))
|
|
}
|
|
|
|
func cacheMergedSwitcherContent(
|
|
_ items: [NSMenuItem],
|
|
in menu: NSMenu,
|
|
selection: ProviderSwitcherSelection,
|
|
context: MergedSwitcherContentCacheContext)
|
|
{
|
|
guard !items.isEmpty else { return }
|
|
|
|
let entry = CachedMergedSwitcherMenuContent(
|
|
requiredMenuContentVersion: context.contentVersion ??
|
|
self.menuSession.renderedVersion(for: ObjectIdentifier(menu)) ??
|
|
self.menuSession.latestRequiredRebuildVersion,
|
|
menuWidth: context.menuWidth,
|
|
codexAccountDisplay: context.codexAccountDisplay,
|
|
tokenAccountDisplay: context.tokenAccountDisplay,
|
|
localizationSignature: self.lastMenuLocalizationSignature,
|
|
items: items)
|
|
self.mergedSwitcherContentCaches[ObjectIdentifier(menu), default: [:]][selection] = entry
|
|
}
|
|
|
|
/// Returns a reusable cached content block, evicting stale entries without attaching them.
|
|
func reusableMergedSwitcherContent(
|
|
for selection: ProviderSwitcherSelection,
|
|
in menu: NSMenu,
|
|
menuWidth: CGFloat,
|
|
codexAccountDisplay: CodexAccountMenuDisplay?,
|
|
tokenAccountDisplay: TokenAccountMenuDisplay?)
|
|
-> [NSMenuItem]?
|
|
{
|
|
let key = ObjectIdentifier(menu)
|
|
guard let entry = self.mergedSwitcherContentCaches[key]?[selection] else { return nil }
|
|
guard entry.matches(
|
|
requiredMenuContentVersion: self.menuSession.latestRequiredRebuildVersion,
|
|
menuWidth: menuWidth,
|
|
codexAccountDisplay: codexAccountDisplay,
|
|
tokenAccountDisplay: tokenAccountDisplay,
|
|
localizationSignature: self.menuLocalizationSignature())
|
|
else {
|
|
self.mergedSwitcherContentCaches[key]?.removeValue(forKey: selection)
|
|
return nil
|
|
}
|
|
return entry.items
|
|
}
|
|
|
|
func addCachedMergedSwitcherContent(
|
|
for selection: ProviderSwitcherSelection,
|
|
to menu: NSMenu,
|
|
menuWidth: CGFloat,
|
|
codexAccountDisplay: CodexAccountMenuDisplay?,
|
|
tokenAccountDisplay: TokenAccountMenuDisplay?)
|
|
-> Bool
|
|
{
|
|
guard let items = self.reusableMergedSwitcherContent(
|
|
for: selection,
|
|
in: menu,
|
|
menuWidth: menuWidth,
|
|
codexAccountDisplay: codexAccountDisplay,
|
|
tokenAccountDisplay: tokenAccountDisplay)
|
|
else { return false }
|
|
|
|
self.lastCodexAccountMenuDisplay = codexAccountDisplay
|
|
self.lastTokenAccountMenuDisplay = tokenAccountDisplay
|
|
for item in items {
|
|
menu.addItem(item)
|
|
}
|
|
// Detached Refresh items cannot observe a completed manual refresh. Recompute only
|
|
// after AppKit has restored their menu so provider-scoped busy state is available.
|
|
self.updatePersistentRefreshItemsEnabled()
|
|
return true
|
|
}
|
|
}
|