170 lines · 6.2 KB · Swift
RawDownload
import AppKitimport CodexBarCoreimport Observationimport SwiftUI extension StatusItemController {    func switcherWeeklyRemaining(for provider: UsageProvider) -> Double? {        Self.switcherWeeklyMetricPercent(            for: provider,            snapshot: self.store.snapshot(for: provider),            showUsed: self.settings.usageBarsShowUsed)    }     func applySubtitle(_ subtitle: String, to item: NSMenuItem, title: String) {        if #available(macOS 14.4, *) {            // NSMenuItem.subtitle is only available on macOS 14.4+.            item.subtitle = subtitle        } else {            item.view = self.makeMenuSubtitleView(title: title, subtitle: subtitle, isEnabled: item.isEnabled)            item.toolTip = "\(title) — \(subtitle)"        }    }     func makeMenuSubtitleView(title: String, subtitle: String, isEnabled: Bool) -> NSView {        let container = NSView()        container.translatesAutoresizingMaskIntoConstraints = false        container.alphaValue = isEnabled ? 1.0 : 0.7         let titleField = NSTextField(labelWithString: title)        titleField.font = NSFont.menuFont(ofSize: NSFont.systemFontSize)        titleField.textColor = NSColor.labelColor        titleField.lineBreakMode = .byTruncatingTail        titleField.maximumNumberOfLines = 1        titleField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)         let subtitleField = NSTextField(labelWithString: subtitle)        subtitleField.font = NSFont.menuFont(ofSize: NSFont.smallSystemFontSize)        subtitleField.textColor = NSColor.secondaryLabelColor        subtitleField.lineBreakMode = .byTruncatingTail        subtitleField.maximumNumberOfLines = 1        subtitleField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)         let stack = NSStackView(views: [titleField, subtitleField])        stack.orientation = .vertical        stack.alignment = .leading        stack.spacing = 1        stack.translatesAutoresizingMaskIntoConstraints = false        container.addSubview(stack)         NSLayoutConstraint.activate([            stack.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 18),            stack.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -10),            stack.topAnchor.constraint(equalTo: container.topAnchor, constant: 2),            stack.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: -2),        ])         return container    }} @MainActorprotocol MenuCardHighlighting: AnyObject {    func setHighlighted(_ highlighted: Bool)} @MainActorprotocol MenuCardMeasuring: AnyObject {    func measuredHeight(width: CGFloat) -> CGFloat} @MainActor@Observablefinal class MenuCardHighlightState {    var isHighlighted = false} final class MenuHostingView<Content: View>: NSHostingView<Content> {    override var allowsVibrancy: Bool {        true    }} @MainActorfinal class MenuCardItemHostingView<Content: View>: NSHostingView<Content>, MenuCardHighlighting, MenuCardMeasuring {    private let highlightState: MenuCardHighlightState    private let onClick: (() -> Void)?     override var allowsVibrancy: Bool {        true    }     override var intrinsicContentSize: NSSize {        let size = super.intrinsicContentSize        guard self.frame.width > 0 else { return size }        return NSSize(width: self.frame.width, height: size.height)    }     init(rootView: Content, highlightState: MenuCardHighlightState, onClick: (() -> Void)? = nil) {        self.highlightState = highlightState        self.onClick = onClick        super.init(rootView: rootView)        if onClick != nil {            let recognizer = NSClickGestureRecognizer(target: self, action: #selector(self.handlePrimaryClick(_:)))            recognizer.buttonMask = 0x1            self.addGestureRecognizer(recognizer)        }    }     required init(rootView: Content) {        self.highlightState = MenuCardHighlightState()        self.onClick = nil        super.init(rootView: rootView)    }     @available(*, unavailable)    required init?(coder: NSCoder) {        fatalError("init(coder:) has not been implemented")    }     override func acceptsFirstMouse(for event: NSEvent?) -> Bool {        true    }     @objc private func handlePrimaryClick(_ recognizer: NSClickGestureRecognizer) {        guard recognizer.state == .ended else { return }        self.onClick?()    }     func measuredHeight(width: CGFloat) -> CGFloat {        let controller = NSHostingController(rootView: self.rootView)        let measured = controller.sizeThatFits(in: CGSize(width: width, height: .greatestFiniteMagnitude))        return measured.height    }     func setHighlighted(_ highlighted: Bool) {        guard self.highlightState.isHighlighted != highlighted else { return }        self.highlightState.isHighlighted = highlighted    }} struct MenuCardSectionContainerView<Content: View>: View {    @Bindable var highlightState: MenuCardHighlightState    let showsSubmenuIndicator: Bool    let submenuIndicatorAlignment: Alignment    let submenuIndicatorTopPadding: CGFloat    @ViewBuilder let content: () -> Content     var body: some View {        self.content()            .environment(\.menuItemHighlighted, self.highlightState.isHighlighted)            .foregroundStyle(MenuHighlightStyle.primary(self.highlightState.isHighlighted))            .background(alignment: .topLeading) {                if self.highlightState.isHighlighted {                    RoundedRectangle(cornerRadius: 6, style: .continuous)                        .fill(MenuHighlightStyle.selectionBackground(true))                        .padding(.horizontal, 6)                        .padding(.vertical, 2)                }            }            .overlay(alignment: self.submenuIndicatorAlignment) {                if self.showsSubmenuIndicator {                    Image(systemName: "chevron.right")                        .font(.caption2.weight(.semibold))                        .foregroundStyle(MenuHighlightStyle.secondary(self.highlightState.isHighlighted))                        .padding(.top, self.submenuIndicatorTopPadding)                        .padding(.trailing, 10)                }            }    }}