aboutsummaryrefslogtreecommitdiff
path: root/Map/Presentation
diff options
context:
space:
mode:
authorRuben Beltran del Rio <git@r.bdr.sh>2024-09-16 11:10:32 +0200
committerRuben Beltran del Rio <git@r.bdr.sh>2024-09-16 11:10:32 +0200
commite2c37ac1dd2ad562e3d619d39b72a174a2212b67 (patch)
treed4da2c0a12db0473eb4030014a92045eebe38834 /Map/Presentation
parentfdb4633d3e9158e457d57e820df6e1efb4df39c2 (diff)
Map 3 first commit: files, groups and layout
Diffstat (limited to 'Map/Presentation')
-rw-r--r--Map/Presentation/Base Components/EvolutionPicker.swift30
-rw-r--r--Map/Presentation/Base Components/MapRender/MapAxes.swift24
-rw-r--r--Map/Presentation/Base Components/MapRender/MapBlockers.swift16
-rw-r--r--Map/Presentation/Base Components/MapRender/MapEdges.swift20
-rw-r--r--Map/Presentation/Base Components/MapRender/MapGroup.swift83
-rw-r--r--Map/Presentation/Base Components/MapRender/MapGroups.swift32
-rw-r--r--Map/Presentation/Base Components/MapRender/MapNotes.swift22
-rw-r--r--Map/Presentation/Base Components/MapRender/MapOpportunities.swift19
-rw-r--r--Map/Presentation/Base Components/MapRender/MapStages.swift49
-rw-r--r--Map/Presentation/Base Components/MapRender/MapVertices.swift41
-rw-r--r--Map/Presentation/Base Components/MapTextEditor.swift89
-rw-r--r--Map/Presentation/Commands/MapCommands.swift44
-rw-r--r--Map/Presentation/Complex Components/MapRender/MapRenderView.swift43
-rw-r--r--Map/Presentation/EvolutionPicker.swift41
-rw-r--r--Map/Presentation/MapEditor.swift134
-rw-r--r--Map/Presentation/Screens/EmptyMapDetailScreen.swift16
-rw-r--r--Map/Presentation/Screens/MapDetailScreen.swift72
-rw-r--r--Map/Presentation/Theme/Color+theme.swift38
-rw-r--r--Map/Presentation/Theme/Dimensions.swift6
-rw-r--r--Map/Presentation/Theme/Font+theme.swift15
-rw-r--r--Map/Presentation/Theme/NSColor+theme.swift14
-rw-r--r--Map/Presentation/ViewStyle.swift3
-rw-r--r--Map/Presentation/Windows/MapEditorWindow.swift109
23 files changed, 605 insertions, 355 deletions
diff --git a/Map/Presentation/Base Components/EvolutionPicker.swift b/Map/Presentation/Base Components/EvolutionPicker.swift
index c68e90c..c30798a 100644
--- a/Map/Presentation/Base Components/EvolutionPicker.swift
+++ b/Map/Presentation/Base Components/EvolutionPicker.swift
@@ -2,40 +2,30 @@ import SwiftUI
struct EvolutionPicker: View {
- @EnvironmentObject private var store: AppStore
-
- private var selectedEvolution: Binding<StageType> {
- Binding(
- get: { store.state.selectedEvolution },
- set: { evolution in
- store.send(.selectEvolution(evolution: evolution))
- }
- )
- }
+ @Binding var selectedEvolution: StageType
var body: some View {
- Picker("Evolution", selection: selectedEvolution) {
+ Picker("Evolution", selection: $selectedEvolution) {
ForEach(StageType.types) { stage in
- Text(Stage.title(stage)).tag(stage).padding(4.0)
+ Text(Stage.title(stage)).font(.theme.body).tag(stage).padding(4.0)
}
Divider()
ForEach(StageType.characteristics) { stage in
- Text(Stage.title(stage)).tag(stage).padding(4.0)
+ Text(Stage.title(stage)).font(.theme.body).tag(stage).padding(4.0).font(.theme.body)
}
Divider()
ForEach(StageType.properties) { stage in
- Text(Stage.title(stage)).tag(stage).padding(4.0)
+ Text(Stage.title(stage)).font(.theme.body).tag(stage).padding(4.0)
}
Divider()
ForEach(StageType.custom) { stage in
- Text(Stage.title(stage)).tag(stage).padding(4.0)
+ Text(Stage.title(stage)).font(.theme.body).tag(stage).padding(4.0)
}
- }.padding(.horizontal, 8.0).padding(.vertical, 4.0)
+ }.font(.theme.body).padding(.horizontal, 8.0).padding(.vertical, 4.0)
}
}
-struct EvolutionPicker_Previews: PreviewProvider {
- static var previews: some View {
- EvolutionPicker()
- }
+#Preview {
+ let selectedEvolution: StageType = .behavior
+ EvolutionPicker(selectedEvolution: .constant(selectedEvolution))
}
diff --git a/Map/Presentation/Base Components/MapRender/MapAxes.swift b/Map/Presentation/Base Components/MapRender/MapAxes.swift
index 6ba758f..1b9c226 100644
--- a/Map/Presentation/Base Components/MapRender/MapAxes.swift
+++ b/Map/Presentation/Base Components/MapRender/MapAxes.swift
@@ -22,10 +22,14 @@ struct MapAxes: View {
}.stroke(Color.map.axisColor, lineWidth: lineWidth * 2)
// Y Labels
- Text("Visible").font(.theme.axisLabel).foregroundColor(.map.labelColor).rotationEffect(Angle(degrees: -90.0))
- .offset(CGSize(width: -35.0, height: 0.0))
- Text("Invisible").font(.theme.axisLabel).foregroundColor(.map.labelColor).rotationEffect(Angle(degrees: -90.0))
- .offset(CGSize(width: -40.0, height: mapSize.height - 20))
+ Text("Visible").font(.theme.axisLabel).foregroundColor(.map.labelColor).rotationEffect(
+ Angle(degrees: -90.0)
+ )
+ .offset(CGSize(width: -35.0, height: 0.0))
+ Text("Invisible").font(.theme.axisLabel).foregroundColor(.map.labelColor).rotationEffect(
+ Angle(degrees: -90.0)
+ )
+ .offset(CGSize(width: -40.0, height: mapSize.height - 20))
// X Labels
@@ -71,11 +75,9 @@ struct MapAxes: View {
}
}
-struct MapAxes_Previews: PreviewProvider {
- static var previews: some View {
- MapAxes(
- mapSize: CGSize(width: 200.0, height: 200.0), lineWidth: CGFloat(1.0),
- evolution: Stage.stages(.general), stages: [25.0, 50.0, 75.0]
- ).padding(50.0)
- }
+#Preview {
+ MapAxes(
+ mapSize: CGSize(width: 200.0, height: 200.0), lineWidth: CGFloat(1.0),
+ evolution: Stage.stages(.general), stages: [25.0, 50.0, 75.0]
+ ).padding(50.0)
}
diff --git a/Map/Presentation/Base Components/MapRender/MapBlockers.swift b/Map/Presentation/Base Components/MapRender/MapBlockers.swift
index d943ba8..efd84fa 100644
--- a/Map/Presentation/Base Components/MapRender/MapBlockers.swift
+++ b/Map/Presentation/Base Components/MapRender/MapBlockers.swift
@@ -31,13 +31,11 @@ struct MapBlockers: View {
}
}
-struct MapBlockers_Previews: PreviewProvider {
- static var previews: some View {
- MapBlockers(
- mapSize: CGSize(width: 400.0, height: 400.0), vertexSize: CGSize(width: 25.0, height: 25.0),
- blockers: [
- Blocker(id: 0, position: CGPoint(x: 50.0, y: 50.0)),
- Blocker(id: 1, position: CGPoint(x: 10.0, y: 20.0)),
- ])
- }
+#Preview {
+ MapBlockers(
+ mapSize: CGSize(width: 400.0, height: 400.0), vertexSize: CGSize(width: 25.0, height: 25.0),
+ blockers: [
+ Blocker(id: 0, position: CGPoint(x: 50.0, y: 50.0)),
+ Blocker(id: 1, position: CGPoint(x: 10.0, y: 20.0)),
+ ])
}
diff --git a/Map/Presentation/Base Components/MapRender/MapEdges.swift b/Map/Presentation/Base Components/MapRender/MapEdges.swift
index 3814495..2af7089 100644
--- a/Map/Presentation/Base Components/MapRender/MapEdges.swift
+++ b/Map/Presentation/Base Components/MapRender/MapEdges.swift
@@ -65,15 +65,13 @@ struct MapEdges: View {
}
}
-struct MapEdges_Previews: PreviewProvider {
- static var previews: some View {
- MapEdges(
- mapSize: CGSize(width: 400.0, height: 400.0), lineWidth: 1.0,
- vertexSize: CGSize(width: 25.0, height: 25.0),
- edges: [
- MapEdge(
- id: 1, origin: CGPoint(x: 2.0, y: 34.0), destination: CGPoint(x: 23.0, y: 76.2),
- arrowhead: true)
- ])
- }
+#Preview {
+ MapEdges(
+ mapSize: CGSize(width: 400.0, height: 400.0), lineWidth: 1.0,
+ vertexSize: CGSize(width: 25.0, height: 25.0),
+ edges: [
+ MapEdge(
+ id: 1, origin: CGPoint(x: 2.0, y: 34.0), destination: CGPoint(x: 23.0, y: 76.2),
+ arrowhead: true)
+ ])
}
diff --git a/Map/Presentation/Base Components/MapRender/MapGroup.swift b/Map/Presentation/Base Components/MapRender/MapGroup.swift
new file mode 100644
index 0000000..9df3338
--- /dev/null
+++ b/Map/Presentation/Base Components/MapRender/MapGroup.swift
@@ -0,0 +1,83 @@
+import ConcaveHull
+import SwiftUI
+
+struct MapGroup: View {
+
+ let mapSize: CGSize
+ let vertexSize: CGSize
+ let group: [Vertex]
+ let color: Color
+
+ let cornerSize = CGSize(width: 2.0, height: 2.0)
+ var strokeSize: CGFloat { 1.75 * vertexSize.width }
+
+ var hull: [CGPoint] {
+ let groupList = group.map({ vertex in
+ return [Double(vertex.position.x), Double(vertex.position.y)]
+ })
+ let hull = Hull()
+ let hullPoints = hull.hull(groupList, nil)
+ return hullPoints.compactMap({ object in
+ if let point = object as? [Double] {
+ return CGPoint(x: point[0], y: point[1])
+ }
+ return nil
+ })
+ }
+
+ var body: some View {
+ Path { path in
+ var initialMove: CGPoint?
+
+ for point in hull {
+ let offsetPoint = CGPoint(x: w(point.x), y: h(point.y))
+
+ if initialMove == nil {
+ path.move(to: offsetPoint)
+ initialMove = offsetPoint
+ } else {
+ path.addLine(to: offsetPoint)
+ }
+ }
+
+ if let initialMove = initialMove {
+ path.addLine(to: initialMove)
+ }
+
+ }
+ .applying(
+ CGAffineTransform(translationX: vertexSize.width / 2.0, y: vertexSize.height / 2.0)
+ )
+ .fill(color)
+ .stroke(
+ color,
+ style: StrokeStyle(
+ lineWidth: strokeSize,
+ lineCap: .round,
+ lineJoin: .round,
+ miterLimit: 0,
+ dash: [],
+ dashPhase: 0
+ )
+ )
+ }
+
+ func h(_ dimension: CGFloat) -> CGFloat {
+ max(0.0, min(mapSize.height, dimension * mapSize.height / 100.0))
+ }
+
+ func w(_ dimension: CGFloat) -> CGFloat {
+ max(0.0, min(mapSize.width, dimension * mapSize.width / 100.0))
+ }
+}
+
+#Preview {
+ MapGroup(
+ mapSize: CGSize(width: 400.0, height: 400.0), vertexSize: CGSize(width: 25.0, height: 25.0),
+ group: [
+ Vertex(id: 0, label: "A Circle", position: CGPoint(x: 50.0, y: 50.0)),
+ Vertex(id: 1, label: "A Square", position: CGPoint(x: 10.0, y: 20.0), shape: .square),
+ Vertex(id: 2, label: "A triangle", position: CGPoint(x: 25, y: 32.0), shape: .triangle),
+ Vertex(id: 3, label: "An X", position: CGPoint(x: 70.0, y: 70.0), shape: .x),
+ ], color: .red)
+}
diff --git a/Map/Presentation/Base Components/MapRender/MapGroups.swift b/Map/Presentation/Base Components/MapRender/MapGroups.swift
new file mode 100644
index 0000000..84f7fb2
--- /dev/null
+++ b/Map/Presentation/Base Components/MapRender/MapGroups.swift
@@ -0,0 +1,32 @@
+import ConcaveHull
+import SwiftUI
+
+struct MapGroups: View {
+
+ let mapSize: CGSize
+ let vertexSize: CGSize
+ let groups: [[Vertex]]
+
+ var body: some View {
+ ForEach(Array(groups.enumerated()), id: \.element) { index, group in
+ MapGroup(mapSize: mapSize, vertexSize: vertexSize, group: group, color: color(index))
+ }
+ }
+
+ private func color(_ index: Int) -> Color {
+ return .map.groupColors[index % Color.map.groupColors.count]
+ }
+}
+
+#Preview {
+ MapGroups(
+ mapSize: CGSize(width: 400.0, height: 400.0), vertexSize: CGSize(width: 25.0, height: 25.0),
+ groups: [
+ [
+ Vertex(id: 0, label: "A Circle", position: CGPoint(x: 50.0, y: 50.0)),
+ Vertex(id: 1, label: "A Square", position: CGPoint(x: 10.0, y: 20.0), shape: .square),
+ Vertex(id: 2, label: "A triangle", position: CGPoint(x: 25, y: 32.0), shape: .triangle),
+ Vertex(id: 3, label: "An X", position: CGPoint(x: 70.0, y: 70.0), shape: .x),
+ ]
+ ])
+}
diff --git a/Map/Presentation/Base Components/MapRender/MapNotes.swift b/Map/Presentation/Base Components/MapRender/MapNotes.swift
index f35b3fe..bbb1aba 100644
--- a/Map/Presentation/Base Components/MapRender/MapNotes.swift
+++ b/Map/Presentation/Base Components/MapRender/MapNotes.swift
@@ -5,13 +5,12 @@ struct MapNotes: View {
let mapSize: CGSize
let lineWidth: CGFloat
let notes: [Note]
-
+
let maxWidth = 400.0
-
var body: some View {
ForEach(notes, id: \.id) { note in
- Text(note.text.replacingOccurrences(of: "\\n", with: "\n")).font(.theme.axisLabel)
+ Text(note.text.replacingOccurrences(of: "\\n", with: "\n")).font(.theme.note)
.padding(2.0)
.background(.white)
.foregroundColor(.map.labelColor)
@@ -35,12 +34,13 @@ struct MapNotes: View {
}
}
-struct MapNotes_Previews: PreviewProvider {
- static var previews: some View {
- MapNotes(
- mapSize: CGSize(width: 400.0, height: 400.0), lineWidth: 1.0,
- notes: [
- Note(id: 0, position: CGPoint(x: 50.0, y: 50.0), text: "Notes can have a lot more text, so we need to make sure that they're resized correctly"),
- ])
- }
+#Preview {
+ MapNotes(
+ mapSize: CGSize(width: 400.0, height: 400.0), lineWidth: 1.0,
+ notes: [
+ Note(
+ id: 0, position: CGPoint(x: 50.0, y: 50.0),
+ text:
+ "Notes can have a lot more text, so we need to make sure that they're resized correctly")
+ ])
}
diff --git a/Map/Presentation/Base Components/MapRender/MapOpportunities.swift b/Map/Presentation/Base Components/MapRender/MapOpportunities.swift
index 7fcadff..b3051ee 100644
--- a/Map/Presentation/Base Components/MapRender/MapOpportunities.swift
+++ b/Map/Presentation/Base Components/MapRender/MapOpportunities.swift
@@ -46,7 +46,8 @@ struct MapOpportunities: View {
path.closeSubpath()
}.applying(
CGAffineTransform(translationX: vertexSize.width / 2.0, y: vertexSize.height / 2.0)
- ).strokedPath(StrokeStyle(lineWidth: lineWidth / 4, dash: [10.0])).stroke(Color.map.opportunityColor)
+ ).strokedPath(StrokeStyle(lineWidth: lineWidth / 4, dash: [10.0])).stroke(
+ Color.map.opportunityColor)
}
}
@@ -59,13 +60,11 @@ struct MapOpportunities: View {
}
}
-struct MapOpportunities_Previews: PreviewProvider {
- static var previews: some View {
- MapOpportunities(
- mapSize: CGSize(width: 400.0, height: 400.0), lineWidth: 1.0,
- vertexSize: CGSize(width: 25.0, height: 25.0),
- opportunities: [
- Opportunity(id: 1, origin: CGPoint(x: 2.0, y: 34.0), destination: CGPoint(x: 23.0, y: 76.2))
- ])
- }
+#Preview {
+ MapOpportunities(
+ mapSize: CGSize(width: 400.0, height: 400.0), lineWidth: 1.0,
+ vertexSize: CGSize(width: 25.0, height: 25.0),
+ opportunities: [
+ Opportunity(id: 1, origin: CGPoint(x: 2.0, y: 34.0), destination: CGPoint(x: 23.0, y: 76.2))
+ ])
}
diff --git a/Map/Presentation/Base Components/MapRender/MapStages.swift b/Map/Presentation/Base Components/MapRender/MapStages.swift
index 0fc8f48..fc3bfa1 100644
--- a/Map/Presentation/Base Components/MapRender/MapStages.swift
+++ b/Map/Presentation/Base Components/MapRender/MapStages.swift
@@ -1,5 +1,5 @@
-import SwiftUI
import Patterns
+import SwiftUI
struct MapStages: View {
@@ -10,17 +10,29 @@ struct MapStages: View {
var body: some View {
ZStack(alignment: .topLeading) {
- PatternView(design: .constant(.stitch), pixelSize: 1.0, foregroundColor: .map.stageForeground, backgroundColor: .map.stageBackground)
- .frame(width: w(stages[0]), height: mapSize.height)
- PatternView(design: .constant(.shingles), pixelSize: 1.0, foregroundColor: .map.stageForeground, backgroundColor: .map.stageBackground)
- .offset(CGSize(width: w(stages[0]), height: 0))
- .frame(width: w(stages[1]) - w(stages[0]), height: mapSize.height)
- PatternView(design: .constant(.shadowGrid), pixelSize: 1.0, foregroundColor: .map.stageForeground, backgroundColor: .map.stageBackground)
- .offset(CGSize(width: w(stages[1]), height: 0))
- .frame(width: w(stages[2]) - w(stages[1]), height: mapSize.height)
- PatternView(design: .constant(.wicker), pixelSize: 1.0, foregroundColor: .map.stageForeground, backgroundColor: .map.stageBackground)
- .offset(CGSize(width: w(stages[2]), height: 0))
- .frame(width: mapSize.width - w(stages[2]), height: mapSize.height)
+ PatternView(
+ design: .constant(.stitch), pixelSize: 1.0, foregroundColor: .map.stageForeground,
+ backgroundColor: .map.stageBackground
+ )
+ .frame(width: w(stages[0]), height: mapSize.height)
+ PatternView(
+ design: .constant(.shingles), pixelSize: 1.0, foregroundColor: .map.stageForeground,
+ backgroundColor: .map.stageBackground
+ )
+ .offset(CGSize(width: w(stages[0]), height: 0))
+ .frame(width: w(stages[1]) - w(stages[0]), height: mapSize.height)
+ PatternView(
+ design: .constant(.shadowGrid), pixelSize: 1.0, foregroundColor: .map.stageForeground,
+ backgroundColor: .map.stageBackground
+ )
+ .offset(CGSize(width: w(stages[1]), height: 0))
+ .frame(width: w(stages[2]) - w(stages[1]), height: mapSize.height)
+ PatternView(
+ design: .constant(.wicker), pixelSize: 1.0, foregroundColor: .map.stageForeground,
+ backgroundColor: .map.stageBackground
+ )
+ .offset(CGSize(width: w(stages[2]), height: 0))
+ .frame(width: mapSize.width - w(stages[2]), height: mapSize.height)
Path { path in
path.move(to: CGPoint(x: w(stages[0]), y: 0))
@@ -34,7 +46,8 @@ struct MapStages: View {
path.closeSubpath()
path.move(to: CGPoint(x: w(stages[0]), y: 0))
path.closeSubpath()
- }.strokedPath(StrokeStyle(lineWidth: lineWidth / 4, dash: [10.0, 18.0])).stroke(Color.map.axisColor)
+ }.strokedPath(StrokeStyle(lineWidth: lineWidth / 4, dash: [10.0, 18.0])).stroke(
+ Color.map.axisColor)
}
}
@@ -43,10 +56,8 @@ struct MapStages: View {
}
}
-struct MapStages_Previews: PreviewProvider {
- static var previews: some View {
- MapStages(
- mapSize: CGSize(width: 200.0, height: 200.0), lineWidth: CGFloat(0.5),
- stages: [25.0, 50.0, 75.0])
- }
+#Preview {
+ MapStages(
+ mapSize: CGSize(width: 200.0, height: 200.0), lineWidth: CGFloat(0.5),
+ stages: [25.0, 50.0, 75.0])
}
diff --git a/Map/Presentation/Base Components/MapRender/MapVertices.swift b/Map/Presentation/Base Components/MapRender/MapVertices.swift
index 74cac6d..5bc0a96 100644
--- a/Map/Presentation/Base Components/MapRender/MapVertices.swift
+++ b/Map/Presentation/Base Components/MapRender/MapVertices.swift
@@ -7,18 +7,29 @@ struct MapVertices: View {
let vertices: [Vertex]
let padding = CGFloat(5.0)
+ var onDragVertex: (Vertex, CGFloat, CGFloat) -> Void = { _, _, _ in }
+
var body: some View {
ZStack(alignment: .topLeading) {
ForEach(vertices, id: \.id) { vertex in
- getVertexShape(vertex).fill(Color.map.vertexColor)
- Text(vertex.label.replacingOccurrences(of: "\\n", with: "\n")).font(.theme.vertexLabel)
+ ZStack(alignment: .topLeading) {
+ getVertexShape(vertex).fill(Color.map.vertexColor)
+ Text(vertex.label.replacingOccurrences(of: "\\n", with: "\n")).font(.theme.vertexLabel)
.foregroundColor(.map.labelColor)
.shadow(color: .white, radius: 0, x: -0.5, y: -0.5)
.shadow(color: .white, radius: 0, x: 0.5, y: 0.5)
.offset(
- CGSize(
- width: w(vertex.position.x) + vertexSize.width + padding,
- height: h(vertex.position.y) + 7.0))
+ CGSize(
+ width: w(vertex.position.x) + vertexSize.width + padding,
+ height: h(vertex.position.y) + 7.0))
+ }.gesture(
+ DragGesture()
+ .onChanged { value in
+ let deltaX = value.startLocation.x - value.location.x
+ let deltaY = value.startLocation.y - value.location.y
+ onDragVertex(vertex, deltaX, deltaY)
+ }
+ )
}
}
}
@@ -78,15 +89,13 @@ struct MapVertices: View {
}
}
-struct MapVertices_Previews: PreviewProvider {
- static var previews: some View {
- MapVertices(
- mapSize: CGSize(width: 400.0, height: 400.0), vertexSize: CGSize(width: 25.0, height: 25.0),
- vertices: [
- Vertex(id: 0, label: "A Circle", position: CGPoint(x: 50.0, y: 50.0)),
- Vertex(id: 1, label: "A Square", position: CGPoint(x: 10.0, y: 20.0), shape: .square),
- Vertex(id: 2, label: "A triangle", position: CGPoint(x: 25, y: 32.0), shape: .triangle),
- Vertex(id: 3, label: "An X", position: CGPoint(x: 70.0, y: 70.0), shape: .x),
- ])
- }
+#Preview {
+ MapVertices(
+ mapSize: CGSize(width: 400.0, height: 400.0), vertexSize: CGSize(width: 25.0, height: 25.0),
+ vertices: [
+ Vertex(id: 0, label: "A Circle", position: CGPoint(x: 50.0, y: 50.0)),
+ Vertex(id: 1, label: "A Square", position: CGPoint(x: 10.0, y: 20.0), shape: .square),
+ Vertex(id: 2, label: "A triangle", position: CGPoint(x: 25, y: 32.0), shape: .triangle),
+ Vertex(id: 3, label: "An X", position: CGPoint(x: 70.0, y: 70.0), shape: .x),
+ ])
}
diff --git a/Map/Presentation/Base Components/MapTextEditor.swift b/Map/Presentation/Base Components/MapTextEditor.swift
index f3838a6..d7aa9f8 100644
--- a/Map/Presentation/Base Components/MapTextEditor.swift
+++ b/Map/Presentation/Base Components/MapTextEditor.swift
@@ -3,7 +3,7 @@ import SwiftUI
class MapTextEditorController: NSViewController {
- @Binding var text: String
+ @Binding var document: MapDocument
let onChange: () -> Void
private let vertexRegex = MapParsingPatterns.vertex
@@ -12,11 +12,12 @@ class MapTextEditorController: NSViewController {
private let opportunityRegex = MapParsingPatterns.opportunity
private let noteRegex = MapParsingPatterns.note
private let stageRegex = MapParsingPatterns.stage
+ private let groupRegex = MapParsingPatterns.group
private let changeDebouncer: Debouncer = Debouncer(seconds: 1)
- init(text: Binding<String>, onChange: @escaping () -> Void) {
- self._text = text
+ init(document: Binding<MapDocument>, onChange: @escaping () -> Void) {
+ self._document = document
self.onChange = onChange
super.init(nibName: nil, bundle: nil)
}
@@ -31,10 +32,11 @@ class MapTextEditorController: NSViewController {
scrollView.translatesAutoresizingMaskIntoConstraints = false
+ textView.backgroundColor = .ui.background
textView.allowsUndo = true
textView.delegate = self
textView.textStorage?.delegate = self
- textView.string = self.text
+ textView.string = self.document.text
textView.isEditable = true
textView.font = .monospacedSystemFont(ofSize: 16.0, weight: .regular)
self.view = scrollView
@@ -49,14 +51,13 @@ extension MapTextEditorController: NSTextViewDelegate {
func textDidChange(_ obj: Notification) {
if let textField = obj.object as? NSTextView {
- self.text = textField.string
-
-
- changeDebouncer.debounce {
- DispatchQueue.main.async {
- self.onChange()
- }
+ self.document.text = textField.string
+
+ changeDebouncer.debounce {
+ DispatchQueue.main.async {
+ self.onChange()
}
+ }
}
}
@@ -86,65 +87,91 @@ extension MapTextEditorController: NSTextStorageDelegate {
var matches = vertexRegex.matches(in: textStorage.string, options: [], range: range)
for match in matches {
- textStorage.addAttributes([.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 1))
- textStorage.addAttributes([.foregroundColor: NSColor.syntax.number], range: match.range(at: 2))
- textStorage.addAttributes([.foregroundColor: NSColor.syntax.number], range: match.range(at: 3))
- textStorage.addAttributes([.foregroundColor: NSColor.syntax.option], range: match.range(at: 4))
+ textStorage.addAttributes(
+ [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 1))
+ textStorage.addAttributes(
+ [.foregroundColor: NSColor.syntax.number], range: match.range(at: 2))
+ textStorage.addAttributes(
+ [.foregroundColor: NSColor.syntax.number], range: match.range(at: 3))
+ textStorage.addAttributes(
+ [.foregroundColor: NSColor.syntax.option], range: match.range(at: 4))
}
matches = edgeRegex.matches(in: textStorage.string, options: [], range: range)
for match in matches {
- textStorage.addAttributes([.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 1))
+ textStorage.addAttributes(
+ [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 1))
let arrowRange = match.range(at: 2)
textStorage.addAttributes(
[.foregroundColor: NSColor.syntax.symbol],
range: NSMakeRange(arrowRange.lowerBound - 1, arrowRange.length + 1))
- textStorage.addAttributes([.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 3))
+ textStorage.addAttributes(
+ [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 3))
}
matches = opportunityRegex.matches(in: textStorage.string, options: [], range: range)
for match in matches {
- textStorage.addAttributes([.foregroundColor: NSColor.syntax.option], range: match.range(at: 1))
- textStorage.addAttributes([.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 2))
- textStorage.addAttributes([.foregroundColor: NSColor.syntax.symbol], range: match.range(at: 3))
- textStorage.addAttributes([.foregroundColor: NSColor.syntax.number], range: match.range(at: 4))
+ textStorage.addAttributes(
+ [.foregroundColor: NSColor.syntax.option], range: match.range(at: 1))
+ textStorage.addAttributes(
+ [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 2))
+ textStorage.addAttributes(
+ [.foregroundColor: NSColor.syntax.symbol], range: match.range(at: 3))
+ textStorage.addAttributes(
+ [.foregroundColor: NSColor.syntax.number], range: match.range(at: 4))
}
matches = blockerRegex.matches(in: textStorage.string, options: [], range: range)
for match in matches {
- textStorage.addAttributes([.foregroundColor: NSColor.syntax.option], range: match.range(at: 1))
- textStorage.addAttributes([.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 2))
+ textStorage.addAttributes(
+ [.foregroundColor: NSColor.syntax.option], range: match.range(at: 1))
+ textStorage.addAttributes(
+ [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 2))
}
-
+
matches = noteRegex.matches(in: textStorage.string, options: [], range: range)
for match in matches {
- textStorage.addAttributes([.foregroundColor: NSColor.syntax.option], range: match.range(at: 1))
- textStorage.addAttributes([.foregroundColor: NSColor.syntax.number], range: match.range(at: 2))
- textStorage.addAttributes([.foregroundColor: NSColor.syntax.number], range: match.range(at: 3))
+ textStorage.addAttributes(
+ [.foregroundColor: NSColor.syntax.option], range: match.range(at: 1))
+ textStorage.addAttributes(
+ [.foregroundColor: NSColor.syntax.number], range: match.range(at: 2))
+ textStorage.addAttributes(
+ [.foregroundColor: NSColor.syntax.number], range: match.range(at: 3))
}
matches = stageRegex.matches(in: textStorage.string, options: [], range: range)
for match in matches {
- textStorage.addAttributes([.foregroundColor: NSColor.syntax.option], range: match.range(at: 1))
- textStorage.addAttributes([.foregroundColor: NSColor.syntax.number], range: match.range(at: 2))
+ textStorage.addAttributes(
+ [.foregroundColor: NSColor.syntax.option], range: match.range(at: 1))
+ textStorage.addAttributes(
+ [.foregroundColor: NSColor.syntax.number], range: match.range(at: 2))
+ }
+
+ matches = groupRegex.matches(in: textStorage.string, options: [], range: range)
+
+ for match in matches {
+ textStorage.addAttributes(
+ [.foregroundColor: NSColor.syntax.option], range: match.range(at: 1))
+ textStorage.addAttributes(
+ [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 2))
}
}
}
struct MapTextEditor: NSViewControllerRepresentable {
- @Binding var text: String
+ @Binding var document: MapDocument
var onChange: () -> Void = {}
func makeNSViewController(
context: NSViewControllerRepresentableContext<MapTextEditor>
) -> MapTextEditorController {
- return MapTextEditorController(text: $text, onChange: onChange)
+ return MapTextEditorController(document: $document, onChange: onChange)
}
func updateNSViewController(
diff --git a/Map/Presentation/Commands/MapCommands.swift b/Map/Presentation/Commands/MapCommands.swift
new file mode 100644
index 0000000..32b4958
--- /dev/null
+++ b/Map/Presentation/Commands/MapCommands.swift
@@ -0,0 +1,44 @@
+import SwiftUI
+
+struct MapCommands: Commands {
+
+ @AppStorage("viewStyle") var viewStyle: ViewStyle = .horizontal
+ @AppStorage("zoom") var zoom = 1.0
+
+ var body: some Commands {
+
+ // View
+
+ CommandGroup(after: CommandGroupPlacement.toolbar) {
+ if viewStyle == .horizontal {
+ Button("Use Vertical Layout") {
+ viewStyle = .vertical
+ }.keyboardShortcut(
+ "l", modifiers: EventModifiers([.command])
+ )
+ } else {
+ Button("Use Horizontal Layout") {
+ viewStyle = .horizontal
+ }.keyboardShortcut(
+ "l", modifiers: EventModifiers([.command])
+ )
+ }
+ Divider()
+ Button("Zoom In") {
+ zoom = min(Constants.kMaxZoom, zoom + 0.1)
+ }.keyboardShortcut(
+ "+", modifiers: EventModifiers([.command])
+ )
+ Button("Zoom Out") {
+ zoom = max(Constants.kMinZoom, zoom - 0.1)
+ }.keyboardShortcut(
+ "-", modifiers: EventModifiers([.command])
+ )
+ Divider()
+ }
+
+ CommandGroup(replacing: CommandGroupPlacement.help) {
+ Button("Map Help") { NSWorkspace.shared.open(URL(string: "https://map.tranquil.systems")!) }
+ }
+ }
+}
diff --git a/Map/Presentation/Complex Components/MapRender/MapRenderView.swift b/Map/Presentation/Complex Components/MapRender/MapRenderView.swift
index b12dabc..b256861 100644
--- a/Map/Presentation/Complex Components/MapRender/MapRenderView.swift
+++ b/Map/Presentation/Complex Components/MapRender/MapRenderView.swift
@@ -5,20 +5,24 @@ import SwiftUI
struct MapRenderView: View {
- @Binding var content: String
+ @Binding var document: MapDocument
@Binding var evolution: StageType
-
+
var stage: Stage {
Stage.stages(evolution)
}
- @State var parsedMap: ParsedMap = ParsedMap.empty
+ var parsedMap: ParsedMap {
+ MapParser.parse(content: document.text)
+ }
- let mapSize = CGSize(width: 1300.0, height: 1000.0)
+ let mapSize = Dimensions.mapSize
+ let padding = Dimensions.mapPadding
let lineWidth = CGFloat(0.5)
let vertexSize = CGSize(width: 25.0, height: 25.0)
- let padding = CGFloat(30.0)
+
+ var onDragVertex: (Vertex, CGFloat, CGFloat) -> Void = { _, _, _ in }
var body: some View {
ZStack(alignment: .topLeading) {
@@ -36,28 +40,27 @@ struct MapRenderView: View {
MapEdges(
mapSize: mapSize, lineWidth: lineWidth, vertexSize: vertexSize, edges: parsedMap.edges)
MapBlockers(mapSize: mapSize, vertexSize: vertexSize, blockers: parsedMap.blockers)
- MapVertices(mapSize: mapSize, vertexSize: vertexSize, vertices: parsedMap.vertices)
+ MapVertices(
+ mapSize: mapSize, vertexSize: vertexSize, vertices: parsedMap.vertices,
+ onDragVertex: onDragVertex)
MapOpportunities(
mapSize: mapSize, lineWidth: lineWidth, vertexSize: vertexSize,
opportunities: parsedMap.opportunities)
+ MapGroups(mapSize: mapSize, vertexSize: vertexSize, groups: parsedMap.groups).drawingGroup(
+ opaque: true
+ ).opacity(0.1)
MapNotes(
mapSize: mapSize, lineWidth: lineWidth, notes: parsedMap.notes)
- }.frame(
- width: mapSize.width,
+ }.offset(x: padding, y: padding).frame(
+ width: mapSize.width + 2 * padding,
height: mapSize.height + 2 * padding, alignment: .topLeading
- ).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: Binding.constant(""), evolution: Binding.constant(StageType.general)
- ).environment(
- \.managedObjectContext, PersistenceController.preview.container.viewContext)
- }
+#Preview {
+ MapRenderView(
+ document: Binding.constant(MapDocument(text: "")),
+ evolution: Binding.constant(StageType.general)
+ )
}
diff --git a/Map/Presentation/EvolutionPicker.swift b/Map/Presentation/EvolutionPicker.swift
new file mode 100644
index 0000000..0f4c954
--- /dev/null
+++ b/Map/Presentation/EvolutionPicker.swift
@@ -0,0 +1,41 @@
+import SwiftUI
+
+struct EvolutionPicker: View {
+
+ @EnvironmentObject private var store: AppStore
+
+ private var selectedEvolution: Binding<StageType> {
+ Binding(
+ get: { store.state.selectedEvolution },
+ set: { evolution in
+ store.send(.selectEvolution(evolution: evolution))
+ }
+ )
+ }
+
+ var body: some View {
+ Picker("Evolution", selection: selectedEvolution) {
+ ForEach(StageType.types) { stage in
+ Text(Stage.title(stage)).font(.theme.body).tag(stage).padding(4.0)
+ }
+ Divider()
+ ForEach(StageType.characteristics) { stage in
+ Text(Stage.title(stage)).font(.theme.body).tag(stage).padding(4.0).font(.theme.body)
+ }
+ Divider()
+ ForEach(StageType.properties) { stage in
+ Text(Stage.title(stage)).font(.theme.body).tag(stage).padding(4.0)
+ }
+ Divider()
+ ForEach(StageType.custom) { stage in
+ Text(Stage.title(stage)).font(.theme.body).tag(stage).padding(4.0)
+ }
+ }.font(.theme.body).padding(.horizontal, 8.0).padding(.vertical, 4.0)
+ }
+}
+
+struct EvolutionPicker_Previews: PreviewProvider {
+ static var previews: some View {
+ EvolutionPicker()
+ }
+}
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")!)
+}
diff --git a/Map/Presentation/Screens/EmptyMapDetailScreen.swift b/Map/Presentation/Screens/EmptyMapDetailScreen.swift
deleted file mode 100644
index f3c6d75..0000000
--- a/Map/Presentation/Screens/EmptyMapDetailScreen.swift
+++ /dev/null
@@ -1,16 +0,0 @@
-import CoreData
-import SwiftUI
-
-struct EmptyMapDetailScreen: View {
-
- var body: some View {
- Text("Select a map from the left hand side, or click on + to create one.")
- }
-}
-
-struct DefaultMapView_Previews: PreviewProvider {
- static var previews: some View {
- EmptyMapDetailScreen().environment(
- \.managedObjectContext, PersistenceController.preview.container.viewContext)
- }
-}
diff --git a/Map/Presentation/Screens/MapDetailScreen.swift b/Map/Presentation/Screens/MapDetailScreen.swift
deleted file mode 100644
index a5c32fd..0000000
--- a/Map/Presentation/Screens/MapDetailScreen.swift
+++ /dev/null
@@ -1,72 +0,0 @@
-import Combine
-import CoreData
-import SwiftUI
-
-struct MapDetailScreen: View {
- @Environment(\.managedObjectContext) private var viewContext
-
- @EnvironmentObject var store: AppStore
-
- @ObservedObject var map: Map
-
- @State var title: String
- @State var content: String
-
- var body: some View {
- if map.uuid != nil {
- VSplitView {
- VStack {
- HStack {
- TextField(
- "Title", text: $title, onCommit: saveModel
- ).font(.title2).textFieldStyle(PlainTextFieldStyle()).padding(.vertical, 4.0).padding(
- .leading, 4.0)
- Button(action: saveText) {
- Image(systemName: "doc.text")
- }.padding(.vertical, 4.0).padding(.leading, 4.0)
- Button(action: saveImage) {
- Image(systemName: "photo")
- }.padding(.vertical, 4.0).padding(.leading, 4.0).padding(.trailing, 8.0)
- }
- EvolutionPicker()
-
- ZStack(alignment: .topLeading) {
- MapTextEditor(text: $content, onChange: saveModel)
- .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)
- }.padding(.horizontal, 8.0)
- ScrollView([.horizontal, .vertical]) {
- MapRenderView(content: $content, evolution: .constant(store.state.selectedEvolution))
- }
- }.onDisappear {
- saveModel()
- }
- } else {
- EmptyMapDetailScreen()
- }
- }
-
- private func saveModel() {
- map.content = content
- map.title = title
- try? viewContext.save()
- }
-
- private func saveText() {
- store.send(.exportMapAsText(map: map))
- }
-
- private func saveImage() {
- store.send(.exportMapAsImage(map: map))
- }
-}
-
-struct MapDetailView_Previews: PreviewProvider {
- static var previews: some View {
- MapDetailScreen(map: Map(), title: "", content: "").environment(
- \.managedObjectContext, PersistenceController.preview.container.viewContext)
- }
-}
diff --git a/Map/Presentation/Theme/Color+theme.swift b/Map/Presentation/Theme/Color+theme.swift
new file mode 100644
index 0000000..1bd9b8e
--- /dev/null
+++ b/Map/Presentation/Theme/Color+theme.swift
@@ -0,0 +1,38 @@
+import SwiftUI
+
+extension Color {
+ struct theme {
+ static let darkSlate = Color("Dark Slate")
+ static let jasperRed = Color("Jasper Red")
+ static let olympicBlue = Color("Olympic Blue")
+ static let lightPorcelainGreen = Color("Light Porcelain Green")
+ static let naplesYellow = Color("Naples Yellow")
+ static let hermosaPink = Color("Hermosa Pink")
+ static let neutralGray = Color("Neutral Gray")
+ static let lightNeutralGray = Color("Light Neutral Gray")
+ static let darkNeutralGray = Color("Dark Neutral Gray")
+ static let darkerNeutralGray = Color("Darker Neutral Gray")
+ }
+
+ struct map {
+ static let labelColor = Color.theme.darkSlate
+ static let axisColor = Color.theme.darkSlate
+ static let vertexColor = Color.theme.darkSlate
+ static let blockerColor = Color.theme.jasperRed
+ static let opportunityColor = Color.theme.olympicBlue
+ static let stageForeground = Color.theme.lightNeutralGray
+ static let stageBackground = Color.white
+ static let groupColors = [
+ Color.theme.olympicBlue,
+ Color.theme.jasperRed,
+ Color.theme.lightPorcelainGreen,
+ Color.theme.naplesYellow,
+ Color.theme.hermosaPink,
+ ]
+ }
+
+ struct ui {
+ static let foreground = Color("Foreground")
+ static let background = Color("Background")
+ }
+}
diff --git a/Map/Presentation/Theme/Dimensions.swift b/Map/Presentation/Theme/Dimensions.swift
new file mode 100644
index 0000000..6a58d58
--- /dev/null
+++ b/Map/Presentation/Theme/Dimensions.swift
@@ -0,0 +1,6 @@
+import Foundation
+
+struct Dimensions {
+ static let mapSize = CGSize(width: 1300.0, height: 1000.0)
+ static let mapPadding: CGFloat = 42.0
+}
diff --git a/Map/Presentation/Theme/Font+theme.swift b/Map/Presentation/Theme/Font+theme.swift
new file mode 100644
index 0000000..07e3b9b
--- /dev/null
+++ b/Map/Presentation/Theme/Font+theme.swift
@@ -0,0 +1,15 @@
+import SwiftUI
+
+extension Font {
+ public struct theme {
+
+ // Map
+ static let note = Font.system(size: 12, design: .serif)
+ static let axisLabel = Font.system(size: 14, design: .serif)
+ static let vertexLabel = Font.system(size: 12, design: .serif)
+
+ // UI
+ static let smallControl = Font.system(size: 9)
+ static let body = Font.system(size: 13)
+ }
+}
diff --git a/Map/Presentation/Theme/NSColor+theme.swift b/Map/Presentation/Theme/NSColor+theme.swift
new file mode 100644
index 0000000..d30332a
--- /dev/null
+++ b/Map/Presentation/Theme/NSColor+theme.swift
@@ -0,0 +1,14 @@
+import AppKit
+
+extension NSColor {
+ struct syntax {
+ static let vertex = NSColor(named: "Vertex") ?? .textColor
+ static let number = NSColor(named: "Number") ?? .textColor
+ static let option = NSColor(named: "Option") ?? .textColor
+ static let symbol = NSColor(named: "Symbol") ?? .textColor
+ }
+
+ struct ui {
+ static let background = NSColor(named: "Background") ?? .windowBackgroundColor
+ }
+}
diff --git a/Map/Presentation/ViewStyle.swift b/Map/Presentation/ViewStyle.swift
new file mode 100644
index 0000000..cea3113
--- /dev/null
+++ b/Map/Presentation/ViewStyle.swift
@@ -0,0 +1,3 @@
+enum ViewStyle: String {
+ case vertical, horizontal
+}
diff --git a/Map/Presentation/Windows/MapEditorWindow.swift b/Map/Presentation/Windows/MapEditorWindow.swift
deleted file mode 100644
index 65316cc..0000000
--- a/Map/Presentation/Windows/MapEditorWindow.swift
+++ /dev/null
@@ -1,109 +0,0 @@
-import CoreData
-import SwiftUI
-
-struct MapEditorWindow: View {
- @Environment(\.managedObjectContext) private var viewContext
-
- @EnvironmentObject var store: AppStore
-
- @FetchRequest(
- sortDescriptors: [NSSortDescriptor(keyPath: \Map.createdAt, ascending: true)],
- animation: .default)
- private var maps: FetchedResults<Map>
-
- var body: some View {
- NavigationView {
- List {
- if maps.count == 0 {
- EmptyMapDetailScreen()
- }
- ForEach(maps) { map in
- NavigationLink(
- destination: MapDetailScreen(map: map, title: map.title ?? "", content: map.content ?? "")
- ) {
- HStack {
- Text(map.title ?? "Untitled Map")
- Spacer()
- Text(mapFormatter.string(from: (map.createdAt ?? Date())))
- .font(.caption)
- .padding(.vertical, 2.0)
- .padding(.horizontal, 4.0)
- .background(Color.accentColor)
- .foregroundColor(Color.black)
- .cornerRadius(2.0)
- }.padding(.leading, 8.0)
- }.contextMenu {
- Button(
- action: { store.send(.deleteMap(map: map)) },
- label: {
- Image(systemName: "trash")
- Text("Delete")
- })
- }
- }
- .onDelete(perform: deleteMaps)
- }.frame(minWidth: 250.0, alignment: .leading)
- .toolbar {
- HStack {
- Button(action: toggleSidebar) {
- Label("Toggle Sidebar", systemImage: "sidebar.left")
- }
- Button(action: addMap) {
- Label("Add Map", systemImage: "plus")
- }
- }
- }
- EmptyMapDetailScreen()
- }
- }
-
- private func toggleSidebar() {
- NSApp.keyWindow?.firstResponder?.tryToPerform(
- #selector(NSSplitViewController.toggleSidebar(_:)), with: nil)
- }
-
- private func addMap() {
- withAnimation {
- let newMap = Map(context: viewContext)
- newMap.uuid = UUID()
- newMap.createdAt = Date()
- newMap.title = "Map \(newMap.createdAt!.format())"
- newMap.content = ""
-
- do {
- try viewContext.save()
- } catch {
- let nsError = error as NSError
- fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
- }
- }
- }
-
- private func deleteMaps(offsets: IndexSet) {
-
- withAnimation {
- offsets.map { maps[$0] }.forEach(viewContext.delete)
-
- do {
- try viewContext.save()
- } catch {
- let nsError = error as NSError
- fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
- }
- }
- }
-}
-
-private let mapFormatter: DateFormatter = {
- let formatter = DateFormatter()
- formatter.dateStyle = .short
- formatter.timeStyle = .none
- return formatter
-}()
-
-struct ContentView_Previews: PreviewProvider {
- static var previews: some View {
- MapEditorWindow().environment(
- \.managedObjectContext, PersistenceController.preview.container.viewContext)
- }
-}