55 lines
2.0 KiB
Swift
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)
|
|
}
|
|
}
|
|
}
|