diff options
Diffstat (limited to 'Map/Presentation/MapEditor.swift')
| -rw-r--r-- | Map/Presentation/MapEditor.swift | 134 |
1 files changed, 134 insertions, 0 deletions
diff --git a/Map/Presentation/MapEditor.swift b/Map/Presentation/MapEditor.swift new file mode 100644 index 0000000..bf33f75 --- /dev/null +++ b/Map/Presentation/MapEditor.swift @@ -0,0 +1,134 @@ +import SwiftUI + +struct MapEditor: View { + @Binding var document: MapDocument + var url: URL? + @State var selectedEvolution: StageType = .behavior + + @AppStorage("viewStyle") var viewStyle: ViewStyle = .horizontal + + let zoomRange = Constants.kMinZoom...Constants.kMaxZoom + @AppStorage("zoom") var zoom = 1.0 + @State var lastZoom = 1.0 + + var body: some View { + VStack(spacing: 0) { + adaptiveStack { + ZStack(alignment: .topLeading) { + MapTextEditor(document: $document) + .background(Color.ui.background) + .foregroundColor(Color.ui.foreground) + .frame(minHeight: 96.0) + }.padding(.top, 8.0).padding(.leading, 8.0).background(Color.ui.background).cornerRadius( + 5.0) + GeometryReader { geometry in + ScrollView([.horizontal, .vertical]) { + MapRenderView( + document: $document, evolution: $selectedEvolution, onDragVertex: onDragVertex + ).scaleEffect(zoom, anchor: .center).frame( + width: (Dimensions.mapSize.width + 2 * Dimensions.mapPadding) * zoom, + height: (Dimensions.mapSize.height + 2 * Dimensions.mapPadding) * zoom) + }.background(Color.ui.background) + .gesture( + MagnificationGesture() + .onChanged { value in + let delta = value / lastZoom + lastZoom = value + zoom = min(max(zoom * delta, zoomRange.lowerBound), zoomRange.upperBound) + } + .onEnded { _ in + lastZoom = 1.0 + } + ) + } + } + Divider() + HStack { + Spacer() + Slider( + value: $zoom, in: zoomRange, step: 0.1, + label: { + Text(formatZoom(zoom)) + .font(.theme.smallControl) + }, + minimumValueLabel: { + Image(systemName: "minus.magnifyingglass") + .font(.theme.smallControl) + .help("Zoom Out (⌘-)") + }, + maximumValueLabel: { + Image(systemName: "plus.magnifyingglass") + .font(.theme.smallControl) + .help("Zoom In (⌘+)") + } + ).frame(width: 200).padding(.trailing, 10.0) + }.padding(4.0) + }.toolbar { + HStack { + Button(action: saveImage) { + Image(systemName: "photo") + } + .help("Export Image (⌘E)") + .padding(.vertical, 4.0).padding(.leading, 4.0).padding(.trailing, 8.0) + } + EvolutionPicker(selectedEvolution: $selectedEvolution) + } + } + + @ViewBuilder + func adaptiveStack<Content: View>(@ViewBuilder content: () -> Content) -> some View { + if viewStyle == .horizontal { + VSplitView { + content() + } + } else { + HSplitView { + content() + } + } + } + + private func formatZoom(_ number: CGFloat) -> String { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.maximumFractionDigits = 1 + formatter.minimumFractionDigits = 1 + return (formatter.string(from: NSNumber(value: number)) ?? "") + "x" + } + + private func onDragVertex(vertex: Vertex, x: CGFloat, y: CGFloat) { + print("Dragging: \(vertex), \(x), \(y)") + } + + private func saveImage() { + if let image = document.exportAsImage(withEvolution: selectedEvolution) { + + let filename = url?.deletingPathExtension().lastPathComponent ?? "Untitled" + + let savePanel = NSSavePanel() + savePanel.allowedContentTypes = [.png] + savePanel.canCreateDirectories = true + savePanel.isExtensionHidden = false + savePanel.title = "Save \(filename) as image" + savePanel.message = "Choose a location to save the image" + savePanel.nameFieldStringValue = "\(filename).png" + savePanel.begin { result in + if result == .OK, let url = savePanel.url { + if let tiffRepresentation = image.tiffRepresentation { + let bitmapImage = NSBitmapImageRep(data: tiffRepresentation) + let pngData = bitmapImage?.representation(using: .png, properties: [:]) + do { + try pngData?.write(to: url) + } catch { + return + } + } + } + } + } + } +} + +#Preview { + MapEditor(document: .constant(MapDocument()), url: URL(filePath: "test.png")!) +} |