diff --git a/DBSyn/SchemaDiagramView.swift b/DBSyn/SchemaDiagramView.swift index 0765ec5..936d63c 100644 --- a/DBSyn/SchemaDiagramView.swift +++ b/DBSyn/SchemaDiagramView.swift @@ -14,6 +14,30 @@ private struct DiagramPoint: Codable { var y: Double } +// Keep the drag translation local to the node. Updating the entire diagram's +// positions dictionary on every mouse event caused layout churn and jerky drags. +private struct DiagramDraggableNode: View { + let zoom: CGFloat + let onDrop: (CGSize) -> Void + @ViewBuilder let content: () -> Content + @GestureState private var translation: CGSize = .zero + + var body: some View { + content() + .offset(x: translation.width / max(zoom, 0.1), + y: translation.height / max(zoom, 0.1)) + .highPriorityGesture( + DragGesture(minimumDistance: 3, coordinateSpace: .global) + .updating($translation) { value, state, _ in + state = value.translation + } + .onEnded { value in + onDrop(value.translation) + } + ) + } +} + @MainActor struct DBSynSchemaDiagramView: View { @ObservedObject var model: DatabaseModel @@ -219,9 +243,17 @@ struct DBSynSchemaDiagramView: View { } } ForEach(objects, id: \.self) { name in - diagramNode(name) - .position(x: position(name).x + nodeWidth / 2, - y: position(name).y + height(name) / 2) + DiagramDraggableNode(zoom: zoom, onDrop: { translation in + let start = position(name) + positions[name] = CGPoint( + x: max(10, start.x + translation.width / max(zoom, 0.1)), + y: max(10, start.y + translation.height / max(zoom, 0.1))) + savePositions() + }) { + diagramNode(name) + } + .position(x: position(name).x + nodeWidth / 2, + y: position(name).y + height(name) / 2) } } .frame(width: canvasWidth, height: canvasHeight, alignment: .topLeading) @@ -317,18 +349,7 @@ struct DBSynSchemaDiagramView: View { .shadow(color: .black.opacity(0.13), radius: 3, y: 2) .opacity(related ? 1 : 0.4) .onTapGesture { selectedObject = selectedObject == name ? nil : name } - .gesture(DragGesture(minimumDistance: 4) - .onChanged { value in - let current = position(name) - // DragGesture.translation is relative to the gesture start. - if dragOrigins[name] == nil { dragOrigins[name] = current } - let start = dragOrigins[name] ?? current - positions[name] = CGPoint(x: max(10, start.x + value.translation.width / zoom), - y: max(10, start.y + value.translation.height / zoom)) - } - .onEnded { _ in dragOrigins.removeValue(forKey: name); savePositions() }) } - @State private var dragOrigins: [String: CGPoint] = [:] // Rebuild the visible canvas whenever the selected objects change. The // previous implementation only saved the checkboxes, leaving the diagram