X-Git-Url: https://git.r.bdr.sh/rbdr/map/blobdiff_plain/e2c37ac1dd2ad562e3d619d39b72a174a2212b67..refs/heads/main:/Map/Presentation/MapEditor.swift?ds=inline diff --git a/Map/Presentation/MapEditor.swift b/Map/Presentation/MapEditor.swift index bf33f75..5cda8f9 100644 --- a/Map/Presentation/MapEditor.swift +++ b/Map/Presentation/MapEditor.swift @@ -1,25 +1,101 @@ +// Copyright (C) 2024 Rubén Beltrán del Río + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see https://map.tranquil.systems. import SwiftUI struct MapEditor: View { @Binding var document: MapDocument var url: URL? @State var selectedEvolution: StageType = .behavior + @State var isSearching: Bool = false + + private let changeDebouncer: Debouncer = Debouncer(seconds: 0.05) @AppStorage("viewStyle") var viewStyle: ViewStyle = .horizontal let zoomRange = Constants.kMinZoom...Constants.kMaxZoom @AppStorage("zoom") var zoom = 1.0 @State var lastZoom = 1.0 + @State var searchTerm = "" + @State var selectedTerm = 0 + + @State var results: [Range] = [] + + private func updateRanges() { + if !isSearching || searchTerm.isEmpty { + results = [] + } + let options: NSString.CompareOptions = [.caseInsensitive, .diacriticInsensitive] + var searchRange = document.text.startIndex..] = [] + + while let range = document.text.range(of: searchTerm, options: options, range: searchRange) { + ranges.append(range) + searchRange = range.upperBound.. 0 { + selectedTerm = (selectedTerm + 1) % results.count + } + } + }, + onPrevious: { + withAnimation { + if results.count > 0 { + if selectedTerm == 0 { + selectedTerm = results.count - 1 + } else { + selectedTerm = (selectedTerm - 1) % results.count + } + } + } + }, + onSubmit: { + + }, + onDismiss: { + isSearching = false + } + ) + .onChange( + of: searchTerm, + { + changeDebouncer.debounce { + updateRanges() + selectedTerm = 0 + } + }) + Divider() + } adaptiveStack { ZStack(alignment: .topLeading) { - MapTextEditor(document: $document) - .background(Color.ui.background) - .foregroundColor(Color.ui.foreground) + MapTextEditor(document: $document, highlightRanges: results, selectedRange: selectedTerm) + .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( + }.padding(.top, 8.0).padding(.leading, 8.0).background(Color.UI.background).cornerRadius( 5.0) GeometryReader { geometry in ScrollView([.horizontal, .vertical]) { @@ -28,7 +104,7 @@ struct MapEditor: View { ).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) + }.background(Color.UI.background) .gesture( MagnificationGesture() .onChanged { value in @@ -49,30 +125,26 @@ struct MapEditor: View { value: $zoom, in: zoomRange, step: 0.1, label: { Text(formatZoom(zoom)) - .font(.theme.smallControl) + .font(.Theme.smallControl) }, minimumValueLabel: { Image(systemName: "minus.magnifyingglass") - .font(.theme.smallControl) + .font(.Theme.smallControl) .help("Zoom Out (⌘-)") }, maximumValueLabel: { Image(systemName: "plus.magnifyingglass") - .font(.theme.smallControl) + .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) } - EvolutionPicker(selectedEvolution: $selectedEvolution) - } + }.focusedSceneValue(\.isSearching, $isSearching) + .focusedSceneValue(\.selectedEvolution, $selectedEvolution) } @ViewBuilder @@ -97,35 +169,6 @@ struct MapEditor: View { } 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 - } - } - } - } - } } }