diff options
| author | Ruben Beltran del Rio <git@r.bdr.sh> | 2024-09-16 20:32:25 +0200 |
|---|---|---|
| committer | Ruben Beltran del Rio <git@r.bdr.sh> | 2024-09-16 20:32:25 +0200 |
| commit | 144915635bdfc90445321189914929a911fe77d4 (patch) | |
| tree | 20f77a976c19f9c8bd138cd3dce06a742acb5113 /Map/Presentation/Base Components | |
| parent | f93215a7b37e065a0a52a852bb2c35d2a3d77b0b (diff) | |
Add search
Diffstat (limited to 'Map/Presentation/Base Components')
| -rw-r--r-- | Map/Presentation/Base Components/MapTextEditor.swift | 75 | ||||
| -rw-r--r-- | Map/Presentation/Base Components/SearchBar.swift | 74 |
2 files changed, 146 insertions, 3 deletions
diff --git a/Map/Presentation/Base Components/MapTextEditor.swift b/Map/Presentation/Base Components/MapTextEditor.swift index 31da790..a7dbe52 100644 --- a/Map/Presentation/Base Components/MapTextEditor.swift +++ b/Map/Presentation/Base Components/MapTextEditor.swift @@ -20,6 +20,19 @@ import SwiftUI class MapTextEditorController: NSViewController { @Binding var document: MapDocument + var highlightRanges: [Range<String.Index>] { + didSet { + updateHighlights() + } + } + + var selectedRange: Int { + didSet { + updateHighlights() + focusOnResult() + } + } + let onChange: () -> Void private let vertexRegex = MapParsingPatterns.vertex @@ -32,9 +45,14 @@ class MapTextEditorController: NSViewController { private let changeDebouncer: Debouncer = Debouncer(seconds: 1) - init(document: Binding<MapDocument>, onChange: @escaping () -> Void) { + init( + document: Binding<MapDocument>, highlightRanges: [Range<String.Index>], selectedRange: Int, + onChange: @escaping () -> Void + ) { self._document = document self.onChange = onChange + self.highlightRanges = highlightRanges + self.selectedRange = selectedRange super.init(nibName: nil, bundle: nil) } @@ -60,6 +78,50 @@ class MapTextEditorController: NSViewController { override func viewDidAppear() { self.view.window?.makeFirstResponder(self.view) + updateHighlights() + } + + private var textView: NSTextView? { + return (view as? NSScrollView)?.documentView as? NSTextView + } + + private func updateHighlights() { + if let textView { + if let textStorage = textView.textStorage { + textStorage.removeAttribute( + .backgroundColor, range: NSRange(location: 0, length: textStorage.length)) + + for range in highlightRanges { + let nsRange = NSRange(range, in: textStorage.string) + + textStorage.addAttribute(.backgroundColor, value: NSColor.syntax.match, range: nsRange) + } + + textView.needsDisplay = true + + } + } + } + + private func focusOnResult() { + if let textView { + if let textStorage = textView.textStorage { + if selectedRange < highlightRanges.count { + let range = highlightRanges[selectedRange] + let nsRange = NSRange(range, in: textStorage.string) + textView.scrollRangeToVisible(nsRange) + textView.selectedRange = nsRange + } + } + } + } + + private func setSelectionColor() { + guard let textView = self.textView else { return } + + var selectedTextAttributes = textView.selectedTextAttributes + selectedTextAttributes[.backgroundColor] = NSColor.yellow.withAlphaComponent(0.3) + textView.selectedTextAttributes = selectedTextAttributes } } @@ -182,16 +244,23 @@ extension MapTextEditorController: NSTextStorageDelegate { struct MapTextEditor: NSViewControllerRepresentable { @Binding var document: MapDocument + var highlightRanges: [Range<String.Index>] + var selectedRange: Int var onChange: () -> Void = {} func makeNSViewController( context: NSViewControllerRepresentableContext<MapTextEditor> ) -> MapTextEditorController { - return MapTextEditorController(document: $document, onChange: onChange) + return MapTextEditorController( + document: $document, highlightRanges: highlightRanges, selectedRange: selectedRange, + onChange: onChange) } func updateNSViewController( _ nsViewController: MapTextEditorController, context: NSViewControllerRepresentableContext<MapTextEditor> - ) {} + ) { + nsViewController.highlightRanges = highlightRanges + nsViewController.selectedRange = selectedRange + } } diff --git a/Map/Presentation/Base Components/SearchBar.swift b/Map/Presentation/Base Components/SearchBar.swift new file mode 100644 index 0000000..7e921b0 --- /dev/null +++ b/Map/Presentation/Base Components/SearchBar.swift @@ -0,0 +1,74 @@ +/* + 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 SearchBar: View { + + @Binding var term: String + @FocusState var isSearchFocused: Bool + var onNext: () -> Void = {} + var onPrevious: () -> Void = {} + var onSubmit: () -> Void = {} + var onDismiss: () -> Void = {} + + var body: some View { + HStack(spacing: 2.0) { + ZStack { + TextField("Search", text: $term) + .textFieldStyle(.roundedBorder) + .padding(.trailing, 16.0) + .focused($isSearchFocused) + .onAppear { + isSearchFocused = true + } + .onKeyPress { press in + if press.key == .return { + if press.modifiers.contains(.shift) { + onPrevious() + return .handled + } + onNext() + return .handled + } + return .ignored + } + } + Spacer() + Button(action: onPrevious) { + Image(systemName: "chevron.left") + .font(.theme.smallControl) + }.keyboardShortcut( + "g", modifiers: EventModifiers([.command, .shift]) + ).help("Find Previous (⇧⌘G)") + Button(action: onNext) { + Image(systemName: "chevron.right") + .font(.theme.smallControl) + }.keyboardShortcut( + "g", modifiers: EventModifiers([.command]) + ).help("Find Next (⌘G)") + Button(action: onDismiss) { + Text("Done") + .font(.theme.smallControl) + }.keyboardShortcut(.escape, modifiers: EventModifiers()) + .help("Done (⎋)") + }.padding(4.0) + } +} + +#Preview { + SearchBar(term: .constant("Hello")) +} |