B539516C25CB0C9300959F72 /* Store.swift in Sources */ = {isa = PBXBuildFile; fileRef = B539516B25CB0C9200959F72 /* Store.swift */; };
B539517425CB0CA400959F72 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B539517325CB0CA400959F72 /* AppState.swift */; };
B539518125CB2D7A00959F72 /* MapTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B539518025CB2D7A00959F72 /* MapTextEditor.swift */; };
+ B587BB6025CDCECB00F328ED /* SlowMapRender.swift in Sources */ = {isa = PBXBuildFile; fileRef = B587BB5F25CDCECB00F328ED /* SlowMapRender.swift */; };
B5CF75C925CC19FC003BFF3D /* EvolutionPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CF75C825CC19FC003BFF3D /* EvolutionPicker.swift */; };
B5CF75CF25CC7965003BFF3D /* MapParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CF75CE25CC7965003BFF3D /* MapParser.swift */; };
B5CF75D825CC79BC003BFF3D /* VertexParserStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CF75D725CC79BC003BFF3D /* VertexParserStrategy.swift */; };
B539516B25CB0C9200959F72 /* Store.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Store.swift; sourceTree = "<group>"; };
B539517325CB0CA400959F72 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
B539518025CB2D7A00959F72 /* MapTextEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTextEditor.swift; sourceTree = "<group>"; };
+ B587BB5F25CDCECB00F328ED /* SlowMapRender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlowMapRender.swift; sourceTree = "<group>"; };
B5CF75C825CC19FC003BFF3D /* EvolutionPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EvolutionPicker.swift; sourceTree = "<group>"; };
B5CF75CE25CC7965003BFF3D /* MapParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapParser.swift; sourceTree = "<group>"; };
B5CF75D725CC79BC003BFF3D /* VertexParserStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VertexParserStrategy.swift; sourceTree = "<group>"; };
B52625A525C876C3003E73B7 /* MapDetail.swift */,
B523C74A25C9C1BA00C44061 /* DefaultMapView.swift */,
B539518025CB2D7A00959F72 /* MapTextEditor.swift */,
+ B587BB5F25CDCECB00F328ED /* SlowMapRender.swift */,
B52625AF25C87C14003E73B7 /* MapRender.swift */,
B5CF75C825CC19FC003BFF3D /* EvolutionPicker.swift */,
);
B526257225C874F9003E73B7 /* MapApp.swift in Sources */,
B52625A625C876C3003E73B7 /* MapDetail.swift in Sources */,
B523C76225CA05A300C44061 /* MapStages.swift in Sources */,
+ B587BB6025CDCECB00F328ED /* SlowMapRender.swift in Sources */,
B5CF75F725CC97CA003BFF3D /* Debouncer.swift in Sources */,
B523C76C25CA0DFA00C44061 /* MapEdges.swift in Sources */,
);
CODE_SIGN_ENTITLEMENTS = Map/Map.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
+ CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_ASSET_PATHS = "\"Map/Preview Content\"";
DEVELOPMENT_TEAM = S68NHQVJXW;
ENABLE_HARDENED_RUNTIME = YES;
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11;
- MARKETING_VERSION = 1.1.0;
+ MARKETING_VERSION = 1.2.0;
PRODUCT_BUNDLE_IDENTIFIER = pizza.unlimited.Map;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
CODE_SIGN_ENTITLEMENTS = Map/Map.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
+ CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_ASSET_PATHS = "\"Map/Preview Content\"";
DEVELOPMENT_TEAM = S68NHQVJXW;
ENABLE_HARDENED_RUNTIME = YES;
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11;
- MARKETING_VERSION = 1.1.0;
+ MARKETING_VERSION = 1.2.0;
PRODUCT_BUNDLE_IDENTIFIER = pizza.unlimited.Map;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
class Debouncer {
// MARK: - Properties
- private let queue = DispatchQueue.main
+ private let queue = DispatchQueue.global(qos: .utility)
private var workItem = DispatchWorkItem(block: {})
private var interval: TimeInterval
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
- <string>1</string>
+ <string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.productivity</string>
<key>LSMinimumSystemVersion</key>
let blockers: [Blocker]
let opportunities: [Opportunity]
let stages: [CGFloat]
+
+ static let empty: ParsedMap = ParsedMap(
+ vertices: [], edges: [], blockers: [], opportunities: [], stages: defaultDimensions)
}
struct Vertex {
window.makeKeyAndOrderFront(nil)
let renderView = MapRenderView(
- content: map.content ?? "", evolution: Stage.stages(state.selectedEvolution))
+ content: Binding.constant(map.content ?? ""),
+ evolution: Binding.constant(Stage.stages(state.selectedEvolution)))
let view = NSHostingView(rootView: renderView)
window.contentView = view
print("Cancel")
}
}
- case .deleteMap(map: let map):
+ case .deleteMap(let map):
let context = PersistenceController.shared.container.viewContext
context.delete(map)
DefaultMapView()
}
ForEach(maps) { map in
- NavigationLink(destination: MapDetailView(map: map)) {
+ NavigationLink(
+ destination: MapDetailView(map: map, title: map.title ?? "", content: map.content ?? "")
+ ) {
HStack {
Text(map.title ?? "Untitled Map")
Spacer()
.cornerRadius(2.0)
}.padding(.leading, 8.0)
}.contextMenu {
- Button(action: { store.send(.deleteMap(map: map))}) {
- Image(systemName: "trash")
- Text("Delete")
- }
+ Button(
+ action: { store.send(.deleteMap(map: map)) },
+ label: {
+ Image(systemName: "trash")
+ Text("Delete")
+ })
}
}
.onDelete(perform: deleteMaps)
}
}
}
- DefaultMapView()
+ DefaultMapView()
}
}
// Created by Ruben Beltran del Rio on 2/1/21.
//
+import Combine
import CoreData
import SwiftUI
+class SaveTimer {
+ let currentTimePublisher = Timer.TimerPublisher(interval: 1, runLoop: .main, mode: .default)
+ let cancellable: AnyCancellable?
+
+ init() {
+ self.cancellable = currentTimePublisher.connect() as? AnyCancellable
+ }
+
+ deinit {
+ self.cancellable?.cancel()
+ }
+}
+
+let timer = SaveTimer()
+
struct MapDetailView: View {
@Environment(\.managedObjectContext) private var viewContext
@Environment(\.colorScheme) var colorScheme
MapColor.colorForScheme(colorScheme)
}
- private var title: Binding<String> {
- Binding(
- get: { map.title ?? "" },
- set: { title in
- map.title = title
- }
- )
- }
-
- private var content: Binding<String> {
- Binding(
- get: { map.content ?? "" },
- set: { content in
- map.content = content
- }
- )
- }
+ @State var title: String
+ @State var content: String
var body: some View {
if map.uuid != nil {
VStack {
HStack {
TextField(
- "Title", text: title,
- onCommit: {
- try? viewContext.save()
- }
+ "Title", text: $title
).font(.title2).textFieldStyle(PlainTextFieldStyle()).padding(.vertical, 4.0).padding(
.leading, 4.0)
Button(action: saveText) {
EvolutionPicker()
ZStack(alignment: .topLeading) {
- MapTextEditor(text: content, colorScheme: colorScheme).onChange(of: map.content) { _ in
- try? viewContext.save()
- }
- .background(mapColor.background)
- .foregroundColor(mapColor.foreground)
- .frame(minHeight: 96.0)
+ MapTextEditor(text: $content, colorScheme: colorScheme)
+ .background(mapColor.background)
+ .foregroundColor(mapColor.foreground)
+ .frame(minHeight: 96.0)
}.padding(.top, 8.0).padding(.leading, 8.0).background(mapColor.background).cornerRadius(
5.0)
}.padding(.horizontal, 8.0)
ScrollView([.horizontal, .vertical]) {
- MapRenderView(
- content: content.wrappedValue, evolution: Stage.stages(store.state.selectedEvolution))
+ SlowMapRender(
+ content: content, evolution: Stage.stages(store.state.selectedEvolution),
+ colorScheme: colorScheme)
}.background(mapColor.background)
+ }.onReceive(timer.currentTimePublisher) { _ in
+ saveModel()
+ }.onDisappear {
+ saveModel()
}
} else {
DefaultMapView()
}
}
+ private func saveModel() {
+ map.content = content
+ map.title = title
+ try? viewContext.save()
+ }
+
private func saveText() {
store.send(.exportMapAsText(map: map))
}
struct MapDetailView_Previews: PreviewProvider {
static var previews: some View {
- MapDetailView(map: Map()).environment(
+ MapDetailView(map: Map(), title: "", content: "").environment(
\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
}
@Environment(\.colorScheme) var colorScheme
- var content: String
- let evolution: Stage
+ @Binding var content: String
+ @Binding var evolution: Stage
+
+ @State var parsedMap: ParsedMap = ParsedMap.empty
let mapSize = CGSize(width: 1300.0, height: 1000.0)
let vertexSize = CGSize(width: 25.0, height: 25.0)
let padding = CGFloat(30.0)
- var parsedMap: ParsedMap {
- return Map.parse(content: content)
- }
-
var body: some View {
ZStack(alignment: .topLeading) {
}.frame(
width: mapSize.width,
height: mapSize.height + 2 * padding, alignment: .topLeading
- ).padding(padding)
+ ).onAppear {
+ self.parsedMap = Map.parse(content: content)
+ }.padding(padding).onChange(of: content) { newState in
+ self.parsedMap = Map.parse(content: newState)
+ }
}
}
struct MapRenderView_Previews: PreviewProvider {
static var previews: some View {
- MapRenderView(content: "", evolution: Stage.stages(.general)).environment(
+ MapRenderView(
+ content: Binding.constant(""), evolution: Binding.constant(Stage.stages(.general))
+ ).environment(
\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
}
scrollView.translatesAutoresizingMaskIntoConstraints = false
+ textView.allowsUndo = true
textView.delegate = self
textView.textStorage?.delegate = self
textView.string = self.text
--- /dev/null
+import Cocoa
+import SwiftUI
+
+class SlowMapRenderController: NSViewController {
+
+ var content: String
+ private var contentBinding: Binding<String> {
+ Binding(
+ get: { self.content },
+ set: { content in
+ self.content = content
+ }
+ )
+ }
+
+ var evolution: Stage
+ private var evolutionBinding: Binding<Stage> {
+ Binding(
+ get: { self.evolution },
+ set: { evolution in
+ self.evolution = evolution
+ }
+ )
+ }
+ private var colorSchemeEnvironment: ObservableColorSchemeEnvironment
+
+ private let debouncer: Debouncer = Debouncer(seconds: 0.1)
+
+ init(content: String, evolution: Stage, colorScheme: ColorScheme) {
+
+ self.content = content
+ self.evolution = evolution
+ self.colorSchemeEnvironment = ObservableColorSchemeEnvironment(colorScheme: colorScheme)
+ super.init(nibName: nil, bundle: nil)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func loadView() {
+
+ let renderView = MapRenderView(content: self.contentBinding, evolution: self.evolutionBinding)
+ .environmentObject(self.colorSchemeEnvironment)
+ let hostingView = NSHostingView(rootView: renderView)
+
+ self.view = hostingView
+ }
+
+ func update(content: String, evolution: Stage, colorScheme: ColorScheme) {
+ self.debouncer.debounce {
+ DispatchQueue.main.async {
+ print("Updating: START")
+ self.content = content
+ self.evolution = evolution
+ self.colorSchemeEnvironment.colorScheme = colorScheme
+ print("Updating: END")
+ }
+ }
+ }
+
+ private class ObservableColorSchemeEnvironment: ObservableObject {
+ @Published var colorScheme: ColorScheme
+
+ init(colorScheme: ColorScheme) {
+ self.colorScheme = colorScheme
+ }
+ }
+}
+
+struct SlowMapRender: NSViewControllerRepresentable {
+
+ var content: String
+ var evolution: Stage
+ let colorScheme: ColorScheme
+
+ func makeNSViewController(
+ context: NSViewControllerRepresentableContext<SlowMapRender>
+ ) -> SlowMapRenderController {
+ return SlowMapRenderController(content: content, evolution: evolution, colorScheme: colorScheme)
+ }
+
+ func updateNSViewController(
+ _ nsViewController: SlowMapRenderController,
+ context: NSViewControllerRepresentableContext<SlowMapRender>
+ ) {
+ nsViewController.update(
+ content: content, evolution: evolution, colorScheme: context.environment.colorScheme)
+ }
+}