From fe86916ce63f95d35b05fbcd092f130eff2a8fa4 Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Fri, 23 Oct 2026 09:08:21 -0700 Subject: [PATCH] Add right-click Copy Image to diagrams --- mac/Sources/Byre/BoxDiagram.swift | 14 ++++++++++++ mac/Sources/Byre/Platform.swift | 19 ++++++++++++++++ mac/Sources/Byre/Render.swift | 37 ++++++++++++++++++++----------- 3 files changed, 57 insertions(+), 13 deletions(-) diff --git a/mac/Sources/Byre/BoxDiagram.swift b/mac/Sources/Byre/BoxDiagram.swift index ae30033..918f3d7 100644 --- a/mac/Sources/Byre/BoxDiagram.swift +++ b/mac/Sources/Byre/BoxDiagram.swift @@ -1103,6 +1103,7 @@ struct BoxDiagramView: View { store.openSheet(.diagram(layout), fromTask: inTask) } .help("Open full size") + .diagramMenu(layout) .frame(maxWidth: .infinity, alignment: .leading) case .failure(let error): DiagramFailure(source: source, reason: error.reason) @@ -1166,6 +1167,19 @@ private struct TappableCanvas: View { .onTapGesture(coordinateSpace: .local) { p in tap(layout.hit(p)) } + .diagramMenu(layout) + } +} + +extension View { + func diagramMenu(_ layout: BoxLayout) -> some View { + contextMenu { + Button("Copy Image") { + if let image = diagramCGImage(layout) { + copyToClipboard(platformImage(image)) + } + } + } } } diff --git a/mac/Sources/Byre/Platform.swift b/mac/Sources/Byre/Platform.swift index ecd2620..9f2dc89 100644 --- a/mac/Sources/Byre/Platform.swift +++ b/mac/Sources/Byre/Platform.swift @@ -31,6 +31,16 @@ func copyToClipboard(_ text: String) { NSPasteboard.general.setString(text, forType: .string) } +@MainActor +func copyToClipboard(_ image: PlatformImage) { + NSPasteboard.general.clearContents() + NSPasteboard.general.writeObjects([image]) +} + +func platformImage(_ cg: CGImage) -> PlatformImage { + NSImage(cgImage: cg, size: NSSize(width: cg.width / 2, height: cg.height / 2)) +} + @MainActor func openExternal(_ url: URL) { NSWorkspace.shared.open(url) @@ -96,6 +106,15 @@ func copyToClipboard(_ text: String) { UIPasteboard.general.string = text } +@MainActor +func copyToClipboard(_ image: PlatformImage) { + UIPasteboard.general.image = image +} + +func platformImage(_ cg: CGImage) -> PlatformImage { + UIImage(cgImage: cg, scale: 2, orientation: .up) +} + @MainActor func openExternal(_ url: URL) { UIApplication.shared.open(url) diff --git a/mac/Sources/Byre/Render.swift b/mac/Sources/Byre/Render.swift index cd2cc53..16aa2b6 100644 --- a/mac/Sources/Byre/Render.swift +++ b/mac/Sources/Byre/Render.swift @@ -1,6 +1,6 @@ -#if os(macOS) import SwiftUI +#if os(macOS) @MainActor func renderDiagram(html: Bool) -> Int32 { FontSize.stored = false @@ -17,18 +17,7 @@ func renderDiagram(html: Bool) -> Int32 { FileHandle.standardOutput.write(Data(diagramHTML(layout).utf8)) return 0 } - let content = BoxCanvas(layout: layout) - .frame(width: layout.size.width, height: layout.size.height) - .padding(16) - .background(Palette.bg) - .environment(\.colorScheme, .light) - let renderer = ImageRenderer(content: content) - renderer.scale = 2 - var png: Data? - NSAppearance(named: .aqua)!.performAsCurrentDrawingAppearance { - png = renderer.cgImage.flatMap { NSBitmapImageRep(cgImage: $0).representation(using: .png, properties: [:]) } - } - guard let png else { + guard let png = diagramCGImage(layout).flatMap({ NSBitmapImageRep(cgImage: $0).representation(using: .png, properties: [:]) }) else { FileHandle.standardError.write(Data("diagram didn't render".utf8)) return 1 } @@ -36,3 +25,25 @@ func renderDiagram(html: Bool) -> Int32 { return 0 } #endif + +@MainActor +func diagramCGImage(_ layout: BoxLayout) -> CGImage? { + let content = BoxCanvas(layout: layout) + .frame(width: layout.size.width, height: layout.size.height) + .padding(16) + .background(Palette.bg) + .environment(\.colorScheme, .light) + let renderer = ImageRenderer(content: content) + renderer.scale = 2 + var image: CGImage? + #if os(macOS) + NSAppearance(named: .aqua)!.performAsCurrentDrawingAppearance { + image = renderer.cgImage + } + #else + UITraitCollection(userInterfaceStyle: .light).performAsCurrent { + image = renderer.cgImage + } + #endif + return image +}