Files
SnipMC/Sources/Editor/EditorWindowController.swift
marcoandClaude Opus 4.6 519706dd41 Fix annotation position shift in editor and export
Use flipped container with Auto Layout for correct coordinate
mapping. Use lockFocusFlipped(true) in renderer so exported
annotations match canvas positions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-22 07:21:05 +02:00

119 lines
4.1 KiB
Swift

import AppKit
import SwiftUI
private final class FlippedContainerView: NSView {
override var isFlipped: Bool { true }
}
final class EditorWindowController: NSWindowController, NSWindowDelegate {
private let annotationDoc = AnnotationDocument()
private let baseImage: NSImage
private let sourceURL: URL?
private let completion: (NSImage?) -> Void
private let canvasView = AnnotationCanvasView()
init(image: NSImage, sourceURL: URL?, completion: @escaping (NSImage?) -> Void) {
self.baseImage = image
self.sourceURL = sourceURL
self.completion = completion
let screenFrame = NSScreen.main?.visibleFrame ?? NSRect(x: 0, y: 0, width: 1280, height: 800)
let maxWidth = screenFrame.width * 0.8
let maxHeight = screenFrame.height * 0.8
let scale = min(maxWidth / image.size.width, maxHeight / image.size.height, 1.0)
let windowWidth = max(image.size.width * scale, 600)
let windowHeight = max(image.size.height * scale, 400) + 52
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: windowWidth, height: windowHeight),
styleMask: [.titled, .closable, .miniaturizable, .resizable],
backing: .buffered,
defer: false
)
window.title = "Bildeditor"
window.isReleasedWhenClosed = false
window.center()
window.minSize = NSSize(width: 500, height: 400)
super.init(window: window)
window.delegate = self
canvasView.baseImage = image
canvasView.document = annotationDoc
canvasView.onDocumentChanged = { [weak self] in
self?.canvasView.needsDisplay = true
}
let toolbarHosting = NSHostingView(rootView: EditorToolbarView(
document: annotationDoc,
onSave: { [weak self] in self?.save() },
onCopyToClipboard: { [weak self] in self?.copyToClipboard() },
onCancel: { [weak self] in self?.cancel() }
))
toolbarHosting.translatesAutoresizingMaskIntoConstraints = false
canvasView.translatesAutoresizingMaskIntoConstraints = false
let container = FlippedContainerView()
container.addSubview(toolbarHosting)
container.addSubview(canvasView)
window.contentView = container
NSLayoutConstraint.activate([
toolbarHosting.topAnchor.constraint(equalTo: container.topAnchor),
toolbarHosting.leadingAnchor.constraint(equalTo: container.leadingAnchor),
toolbarHosting.trailingAnchor.constraint(equalTo: container.trailingAnchor),
toolbarHosting.heightAnchor.constraint(equalToConstant: 52),
canvasView.topAnchor.constraint(equalTo: toolbarHosting.bottomAnchor),
canvasView.leadingAnchor.constraint(equalTo: container.leadingAnchor),
canvasView.trailingAnchor.constraint(equalTo: container.trailingAnchor),
canvasView.bottomAnchor.constraint(equalTo: container.bottomAnchor),
])
}
@available(*, unavailable)
required init?(coder: NSCoder) { fatalError() }
func showEditor() {
NSApplication.shared.setActivationPolicy(.regular)
NSApplication.shared.activate(ignoringOtherApps: true)
window?.makeKeyAndOrderFront(nil)
window?.makeFirstResponder(canvasView)
}
// MARK: - Actions
private func renderFinal() -> NSImage {
AnnotationRenderer.render(annotations: annotationDoc.items, onto: baseImage)
}
private func save() {
let image = renderFinal()
completion(image)
close()
}
private func copyToClipboard() {
let image = renderFinal()
let pasteboard = NSPasteboard.general
pasteboard.clearContents()
pasteboard.writeObjects([image])
close()
}
private func cancel() {
completion(nil)
close()
}
// MARK: - NSWindowDelegate
func windowWillClose(_ notification: Notification) {
let visibleWindows = NSApplication.shared.windows.filter { $0.isVisible && $0 != window }
if visibleWindows.isEmpty {
NSApplication.shared.setActivationPolicy(.accessory)
}
}
}