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>
This commit is contained in:
@@ -28,9 +28,11 @@ enum AnnotationRenderer {
|
|||||||
static func render(annotations: [AnnotationItem], onto image: NSImage) -> NSImage {
|
static func render(annotations: [AnnotationItem], onto image: NSImage) -> NSImage {
|
||||||
let size = image.size
|
let size = image.size
|
||||||
let result = NSImage(size: size)
|
let result = NSImage(size: size)
|
||||||
result.lockFocus()
|
result.lockFocusFlipped(true)
|
||||||
|
|
||||||
image.draw(in: NSRect(origin: .zero, size: size))
|
image.draw(in: NSRect(origin: .zero, size: size),
|
||||||
|
from: .zero, operation: .sourceOver, fraction: 1,
|
||||||
|
respectFlipped: true, hints: nil)
|
||||||
|
|
||||||
if let context = NSGraphicsContext.current?.cgContext {
|
if let context = NSGraphicsContext.current?.cgContext {
|
||||||
for item in annotations {
|
for item in annotations {
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import AppKit
|
import AppKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
private final class FlippedContainerView: NSView {
|
||||||
|
override var isFlipped: Bool { true }
|
||||||
|
}
|
||||||
|
|
||||||
final class EditorWindowController: NSWindowController, NSWindowDelegate {
|
final class EditorWindowController: NSWindowController, NSWindowDelegate {
|
||||||
|
|
||||||
private let annotationDoc = AnnotationDocument()
|
private let annotationDoc = AnnotationDocument()
|
||||||
@@ -19,7 +23,7 @@ final class EditorWindowController: NSWindowController, NSWindowDelegate {
|
|||||||
let maxHeight = screenFrame.height * 0.8
|
let maxHeight = screenFrame.height * 0.8
|
||||||
let scale = min(maxWidth / image.size.width, maxHeight / image.size.height, 1.0)
|
let scale = min(maxWidth / image.size.width, maxHeight / image.size.height, 1.0)
|
||||||
let windowWidth = max(image.size.width * scale, 600)
|
let windowWidth = max(image.size.width * scale, 600)
|
||||||
let windowHeight = max(image.size.height * scale, 400) + 52 // toolbar height
|
let windowHeight = max(image.size.height * scale, 400) + 52
|
||||||
|
|
||||||
let window = NSWindow(
|
let window = NSWindow(
|
||||||
contentRect: NSRect(x: 0, y: 0, width: windowWidth, height: windowHeight),
|
contentRect: NSRect(x: 0, y: 0, width: windowWidth, height: windowHeight),
|
||||||
@@ -40,7 +44,6 @@ final class EditorWindowController: NSWindowController, NSWindowDelegate {
|
|||||||
canvasView.onDocumentChanged = { [weak self] in
|
canvasView.onDocumentChanged = { [weak self] in
|
||||||
self?.canvasView.needsDisplay = true
|
self?.canvasView.needsDisplay = true
|
||||||
}
|
}
|
||||||
canvasView.autoresizingMask = [.width, .height]
|
|
||||||
|
|
||||||
let toolbarHosting = NSHostingView(rootView: EditorToolbarView(
|
let toolbarHosting = NSHostingView(rootView: EditorToolbarView(
|
||||||
document: annotationDoc,
|
document: annotationDoc,
|
||||||
@@ -48,23 +51,25 @@ final class EditorWindowController: NSWindowController, NSWindowDelegate {
|
|||||||
onCopyToClipboard: { [weak self] in self?.copyToClipboard() },
|
onCopyToClipboard: { [weak self] in self?.copyToClipboard() },
|
||||||
onCancel: { [weak self] in self?.cancel() }
|
onCancel: { [weak self] in self?.cancel() }
|
||||||
))
|
))
|
||||||
toolbarHosting.translatesAutoresizingMaskIntoConstraints = true
|
toolbarHosting.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
canvasView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
let container = NSView(frame: window.contentRect(forFrameRect: window.frame))
|
|
||||||
container.autoresizingMask = [.width, .height]
|
|
||||||
|
|
||||||
toolbarHosting.frame = NSRect(x: 0, y: container.bounds.height - 52,
|
|
||||||
width: container.bounds.width, height: 52)
|
|
||||||
toolbarHosting.autoresizingMask = [.width, .minYMargin]
|
|
||||||
|
|
||||||
canvasView.frame = NSRect(x: 0, y: 0,
|
|
||||||
width: container.bounds.width,
|
|
||||||
height: container.bounds.height - 52)
|
|
||||||
canvasView.autoresizingMask = [.width, .height]
|
|
||||||
|
|
||||||
|
let container = FlippedContainerView()
|
||||||
container.addSubview(toolbarHosting)
|
container.addSubview(toolbarHosting)
|
||||||
container.addSubview(canvasView)
|
container.addSubview(canvasView)
|
||||||
window.contentView = container
|
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)
|
@available(*, unavailable)
|
||||||
|
|||||||
Reference in New Issue
Block a user