chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
import AppKit
|
||||
import SwiftUI
|
||||
|
||||
extension StatusItemController {
|
||||
func refreshMenuCardHeights(in menu: NSMenu) {
|
||||
let width = self.renderedMenuWidth(for: menu)
|
||||
for item in menu.items {
|
||||
if let view = item.view as? PersistentRefreshMenuView {
|
||||
guard abs(view.frame.width - width) > 0.5 else { continue }
|
||||
view.applySize(width: width, height: PersistentRefreshRowMetrics.defaults.rowHeight)
|
||||
continue
|
||||
}
|
||||
guard let view = item.view, view is any MenuCardMeasuring else { continue }
|
||||
guard abs(view.frame.width - width) > 0.5 else { continue }
|
||||
let id = item.representedObject as? String ?? "menuCard"
|
||||
let scope = self.menuProvider(for: menu)?.rawValue ?? id
|
||||
let height = self.cachedMenuCardHeight(for: id, scope: scope, width: width) {
|
||||
self.menuCardHeight(for: view, width: width)
|
||||
}
|
||||
view.frame = NSRect(
|
||||
origin: .zero,
|
||||
size: NSSize(width: width, height: height))
|
||||
}
|
||||
}
|
||||
|
||||
func makeMenuCardItem<CardContent: View>(
|
||||
_ view: CardContent,
|
||||
id: String,
|
||||
width: CGFloat,
|
||||
heightCacheScope: String? = nil,
|
||||
heightCacheFingerprint: String? = nil,
|
||||
submenu: NSMenu? = nil,
|
||||
submenuIndicatorAlignment: Alignment = .topTrailing,
|
||||
submenuIndicatorTopPadding: CGFloat = 8,
|
||||
containsInteractiveControls: Bool = false,
|
||||
usesGPUSelection: Bool = false,
|
||||
onClick: (() -> Void)? = nil) -> NSMenuItem
|
||||
{
|
||||
let allowsMenuHighlight = submenu != nil || onClick != nil
|
||||
if !self.menuCardRenderingEnabledForController {
|
||||
let item = NSMenuItem()
|
||||
item.isEnabled = allowsMenuHighlight
|
||||
item.representedObject = id
|
||||
item.submenu = submenu
|
||||
if submenu != nil {
|
||||
item.target = self
|
||||
item.action = #selector(self.menuCardNoOp(_:))
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
if usesGPUSelection {
|
||||
// Selection is painted by AppKit/GPU, so the SwiftUI content is pinned to its normal
|
||||
// appearance via a `highlightState` that is never flipped; these rows skip hosting-view
|
||||
// recycling because the recycler is typed to `MenuCardItemHostingView`.
|
||||
let interactiveRegionStore = MenuCardInteractiveRegionStore()
|
||||
let wrapped = MenuCardSectionContainerView(
|
||||
highlightState: MenuCardHighlightState(),
|
||||
showsSubmenuIndicator: submenu != nil,
|
||||
submenuIndicatorAlignment: submenuIndicatorAlignment,
|
||||
submenuIndicatorTopPadding: submenuIndicatorTopPadding,
|
||||
refreshMonitor: self.menuCardRefreshMonitor,
|
||||
interactiveRegionStore: interactiveRegionStore)
|
||||
{
|
||||
view
|
||||
}
|
||||
let gpuHosting = GPUSelectionHostingView(
|
||||
rootView: wrapped,
|
||||
allowsMenuHighlight: allowsMenuHighlight,
|
||||
containsInteractiveControls: containsInteractiveControls,
|
||||
interactiveRegionStore: interactiveRegionStore,
|
||||
onClick: onClick)
|
||||
let gpuHeight = self.cachedMenuCardHeight(
|
||||
for: id,
|
||||
scope: heightCacheScope ?? id,
|
||||
width: width,
|
||||
fingerprint: heightCacheFingerprint)
|
||||
{
|
||||
self.menuCardHeight(for: gpuHosting, width: width)
|
||||
}
|
||||
gpuHosting.frame = NSRect(origin: .zero, size: NSSize(width: width, height: gpuHeight))
|
||||
return self.makeMenuCardNSMenuItem(
|
||||
hosting: gpuHosting,
|
||||
id: id,
|
||||
submenu: submenu,
|
||||
isEnabled: allowsMenuHighlight || containsInteractiveControls)
|
||||
}
|
||||
|
||||
let hosting: MenuCardItemHostingView<MenuCardSectionContainerView<CardContent>>
|
||||
if let recycled = self.takeRecyclableMenuCardView(
|
||||
for: id,
|
||||
as: MenuCardItemHostingView<MenuCardSectionContainerView<CardContent>>.self)
|
||||
{
|
||||
let wrapped = MenuCardSectionContainerView(
|
||||
highlightState: recycled.highlightState,
|
||||
showsSubmenuIndicator: submenu != nil,
|
||||
submenuIndicatorAlignment: submenuIndicatorAlignment,
|
||||
submenuIndicatorTopPadding: submenuIndicatorTopPadding,
|
||||
refreshMonitor: self.menuCardRefreshMonitor,
|
||||
interactiveRegionStore: recycled.interactiveRegionStore)
|
||||
{
|
||||
view
|
||||
}
|
||||
recycled.prepareForReuse(
|
||||
rootView: wrapped,
|
||||
allowsMenuHighlight: allowsMenuHighlight,
|
||||
containsInteractiveControls: containsInteractiveControls,
|
||||
onClick: onClick)
|
||||
hosting = recycled
|
||||
} else {
|
||||
let highlightState = MenuCardHighlightState()
|
||||
let interactiveRegionStore = MenuCardInteractiveRegionStore()
|
||||
let wrapped = MenuCardSectionContainerView(
|
||||
highlightState: highlightState,
|
||||
showsSubmenuIndicator: submenu != nil,
|
||||
submenuIndicatorAlignment: submenuIndicatorAlignment,
|
||||
submenuIndicatorTopPadding: submenuIndicatorTopPadding,
|
||||
refreshMonitor: self.menuCardRefreshMonitor,
|
||||
interactiveRegionStore: interactiveRegionStore)
|
||||
{
|
||||
view
|
||||
}
|
||||
hosting = MenuCardItemHostingView(
|
||||
rootView: wrapped,
|
||||
highlightState: highlightState,
|
||||
allowsMenuHighlight: allowsMenuHighlight,
|
||||
containsInteractiveControls: containsInteractiveControls,
|
||||
interactiveRegionStore: interactiveRegionStore,
|
||||
onClick: onClick)
|
||||
}
|
||||
let height = self.cachedMenuCardHeight(
|
||||
for: id,
|
||||
scope: heightCacheScope ?? id,
|
||||
width: width,
|
||||
fingerprint: heightCacheFingerprint)
|
||||
{
|
||||
self.menuCardHeight(for: hosting, width: width)
|
||||
}
|
||||
hosting.frame = NSRect(origin: .zero, size: NSSize(width: width, height: height))
|
||||
return self.makeMenuCardNSMenuItem(
|
||||
hosting: hosting,
|
||||
id: id,
|
||||
submenu: submenu,
|
||||
isEnabled: allowsMenuHighlight || containsInteractiveControls)
|
||||
}
|
||||
|
||||
/// Wraps a measured hosting view in the `NSMenuItem` the menu installs, wiring submenu routing.
|
||||
private func makeMenuCardNSMenuItem(
|
||||
hosting: NSView,
|
||||
id: String,
|
||||
submenu: NSMenu?,
|
||||
isEnabled: Bool) -> NSMenuItem
|
||||
{
|
||||
let item = NSMenuItem()
|
||||
item.view = hosting
|
||||
item.isEnabled = isEnabled
|
||||
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
|
||||
|
||||
if let measured = view as? MenuCardMeasuring {
|
||||
return max(1, ceil(measured.measuredHeight(width: width) + basePadding + descenderSafety))
|
||||
}
|
||||
|
||||
view.frame = NSRect(origin: .zero, size: NSSize(width: width, height: 1))
|
||||
let fitted = view.fittingSize
|
||||
return max(1, ceil(fitted.height + basePadding + descenderSafety))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user