]> git.r.bdr.sh - rbdr/map/blobdiff - Map/Presentation/MapEditor.swift
Bump build version
[rbdr/map] / Map / Presentation / MapEditor.swift
index bf33f750849df870ff559b0f80c2e91ba3daffc8..d62d4adfea1bf2259247fa32b7bc874defdbf463 100644 (file)
@@ -1,25 +1,96 @@
+// 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
 
   @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
+
+  var results: [Range<String.Index>] {
+    if !isSearching || searchTerm.isEmpty {
+      return []
+    }
+    let options: NSString.CompareOptions = [.caseInsensitive, .diacriticInsensitive]
+    var searchRange = document.text.startIndex..<document.text.endIndex
+    var ranges: [Range<String.Index>] = []
+
+    while let range = document.text.range(of: searchTerm, options: options, range: searchRange) {
+      ranges.append(range)
+      searchRange = range.upperBound..<document.text.endIndex
+    }
+
+    return ranges
+
+  }
 
   var body: some View {
     VStack(spacing: 0) {
+      if isSearching {
+        SearchBar(
+          term: $searchTerm,
+          onNext: {
+            withAnimation {
+              if results.count > 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: {
+            withAnimation {
+              isSearching = false
+            }
+          }
+        )
+        .onChange(
+          of: searchTerm,
+          {
+            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 +99,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 +120,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 +164,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
-            }
-          }
-        }
-      }
-    }
   }
 }