Files
steipete--codexbar/Sources/CodexBar/StatusItemController+MenuCardHeightCache.swift
T
2026-07-13 12:22:33 +08:00

55 lines
2.0 KiB
Swift

import AppKit
extension StatusItemController {
struct MenuCardHeightCacheKey: Hashable {
let id: String
let scope: String
let width: Int
let textScale: Int
let fingerprint: String
}
/// Measured card height also depends on the resolved font sizes, which the menu cards
/// derive from semantic text styles (`.body`, `.footnote`, …). Those scale with the
/// macOS system text-size / Dynamic Type setting, which is neither part of the content
/// fingerprint nor invalidated on rebuild. Fold the current resolved scale into the key
/// so a runtime text-size change forces a fresh measurement instead of returning a
/// height measured at the old scale (clipped / over-tall cards).
static func menuCardHeightTextScaleToken() -> Int {
Int((NSFont.preferredFont(forTextStyle: .body).pointSize * 100).rounded())
}
func cachedMenuCardHeight(
for id: String,
scope: String,
width: CGFloat,
fingerprint: String? = nil,
measure: () -> CGFloat) -> CGFloat
{
let key = MenuCardHeightCacheKey(
id: id,
scope: scope,
width: Int((width * 100).rounded()),
textScale: Self.menuCardHeightTextScaleToken(),
fingerprint: fingerprint ?? "version:\(self.menuSession.contentVersion)")
if let cached = self.menuCardHeightCache[key] {
return cached
}
let height = measure()
if self.menuCardHeightCache.count > 256 {
self.menuCardHeightCache.removeAll(keepingCapacity: true)
}
self.menuCardHeightCache[key] = height
return height
}
func pruneVersionScopedMenuCardHeightCache() {
let currentVersionFingerprint = "version:\(self.menuSession.contentVersion)"
for key in self.menuCardHeightCache.keys
where key.fingerprint.hasPrefix("version:") && key.fingerprint != currentVersionFingerprint
{
self.menuCardHeightCache.removeValue(forKey: key)
}
}
}