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

78 lines
2.2 KiB
Swift

import AppKit
import SwiftUI
@MainActor
enum MenuPasteboardCopy {
typealias DeferredAction = @MainActor @Sendable () -> Void
typealias Scheduler = @MainActor @Sendable (@escaping DeferredAction) -> Void
typealias Writer = @MainActor @Sendable (String) -> Void
static func perform(
_ text: String,
scheduler: Scheduler = Self.schedule,
writer: @escaping Writer = Self.write,
completion: @escaping DeferredAction = {})
{
scheduler {
writer(text)
completion()
}
}
private static func schedule(_ action: @escaping DeferredAction) {
DispatchQueue.main.async(execute: action)
}
private static func write(_ text: String) {
let pasteboard = NSPasteboard.general
pasteboard.clearContents()
pasteboard.setString(text, forType: .string)
}
}
struct ClickToCopyOverlay: NSViewRepresentable {
let copyText: String
func makeNSView(context: Context) -> ClickToCopyView {
ClickToCopyView(copyText: self.copyText)
}
func updateNSView(_ nsView: ClickToCopyView, context: Context) {
// Guard against no-op writes to avoid AppKit view invalidation on every
// parent card SwiftUI diff (each MenuCardView body re-eval runs through
// .overlay { ClickToCopyOverlay(...) }, which calls updateNSView even
// when copyText is unchanged).
guard nsView.copyText != self.copyText else { return }
nsView.copyText = self.copyText
}
}
final class ClickToCopyView: NSView {
var copyText: String
private let copyAction: (String) -> Void
init(
copyText: String,
copyAction: @escaping (String) -> Void = { MenuPasteboardCopy.perform($0) })
{
self.copyText = copyText
self.copyAction = copyAction
super.init(frame: .zero)
self.wantsLayer = false
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func acceptsFirstMouse(for event: NSEvent?) -> Bool {
true
}
override func mouseDown(with event: NSEvent) {
_ = event
self.copyAction(self.copyText)
}
}