diff options
| author | Ruben Beltran del Rio <git@r.bdr.sh> | 2025-07-09 08:43:04 +0200 |
|---|---|---|
| committer | Ruben Beltran del Rio <git@r.bdr.sh> | 2025-07-09 08:43:04 +0200 |
| commit | 1e89ea22d551cebfd6c0c0451000c3283ddb2d2e (patch) | |
| tree | 77afac88484877fbea5fecd1ddda85741584c6c5 /Map | |
| parent | 1ec7f2c6deb64f5b13b32fae2bf74ae2d8aaaabb (diff) | |
Get rid of old map parser
Diffstat (limited to 'Map')
10 files changed, 212 insertions, 666 deletions
diff --git a/Map/Business/MapParser/MapParser.swift b/Map/Business/MapParser/MapParser.swift deleted file mode 100644 index fc11415..0000000 --- a/Map/Business/MapParser/MapParser.swift +++ /dev/null @@ -1,215 +0,0 @@ -// 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 CoreGraphics -import Foundation - -struct MapParser { - static func parse(content: String) -> ParsedMap { - - let parsers = [ - AnyMapParserStrategy(NoteParserStrategy()), - AnyMapParserStrategy(VertexParserStrategy()), - AnyMapParserStrategy(EdgeParserStrategy()), - AnyMapParserStrategy(InertiaParserStrategy()), - AnyMapParserStrategy(OpportunityParserStrategy()), - AnyMapParserStrategy(StageParserStrategy()), - AnyMapParserStrategy(GroupParserStrategy()), - ] - let builder = MapBuilder() - - let lines = content.split(whereSeparator: \.isNewline) - - for (index, line) in lines.enumerated() { - for parser in parsers { - if parser.canHandle(line: String(line)) { - let (type, object) = parser.handle( - index: index, line: String(line), vertices: builder.vertices) - builder.addObjectToMap(type: type, object: object) - break - } - } - } - - return builder.build() - } -} - -// MARK: - Types - -struct MapParsingPatterns { - static let vertex = try! NSRegularExpression( - pattern: - "^([^\\(\\[\\]]*?)[\\s]*\\([\\s]*([0-9]+.?[0-9]*)[\\s]*,[\\s]*([0-9]+.?[0-9]*)[\\s]*\\)[\\s]*(?:\\[(.*?)\\])?[\\s]*$", - options: [.caseInsensitive, .anchorsMatchLines]) - static let edge = try! NSRegularExpression( - pattern: "^(.+?)[\\s]*-([->])[\\s]*(.+)", options: [.caseInsensitive, .anchorsMatchLines]) - static let inertia = try! NSRegularExpression( - pattern: "^\\[(Inertia)\\][\\s]*(.+)", options: [.caseInsensitive, .anchorsMatchLines]) - static let opportunity = try! NSRegularExpression( - pattern: "^\\[(Evolution)\\][\\s]*(.+)[\\s]+([-+])[\\s]*([0-9]+.?[0-9]*)", - options: [.caseInsensitive, .anchorsMatchLines]) - static let note = try! NSRegularExpression( - pattern: - "^\\[(Note)\\][\\s]*\\([\\s]*([0-9]+.?[0-9]*)[\\s]*,[\\s]*([0-9]+.?[0-9]*)[\\s]*\\)[\\s]*(.*)", - options: [.caseInsensitive, .anchorsMatchLines]) - static let stage = try! NSRegularExpression( - pattern: "^\\[(I{1,3})\\][\\s]*([0-9]+.?[0-9]*)", - options: [.caseInsensitive, .anchorsMatchLines]) - static let group = try! NSRegularExpression( - pattern: "^\\[(Group)\\][\\s]*(.+)", options: [.caseInsensitive, .anchorsMatchLines]) -} - -struct ParsedMap { - let vertices: [Vertex] - let edges: [MapEdge] - let inertias: [Inertia] - let opportunities: [Opportunity] - let notes: [Note] - let stages: [CGFloat] - let groups: [[Vertex]] - - static let empty: ParsedMap = ParsedMap( - vertices: [], edges: [], inertias: [], opportunities: [], notes: [], stages: defaultDimensions, - groups: []) -} - -struct Vertex: Identifiable, Hashable { - let id: Int - let label: String - let position: CGPoint - var shape: VertexShape = .circle -} - -struct Note { - let id: Int - let position: CGPoint - let text: String -} - -enum VertexShape: String { - case circle - case square - case triangle - case x -} - -struct MapEdge { - let id: Int - let origin: CGPoint - let destination: CGPoint - let arrowhead: Bool -} - -struct Inertia { - let id: Int - let position: CGPoint -} - -struct Opportunity { - let id: Int - let origin: CGPoint - let destination: CGPoint -} - -struct StageDimensions { - let index: Int - let dimensions: CGFloat -} - -private let defaultDimensions: [CGFloat] = [ - 25.0, - 50.0, - 75.0, -] - -// MARK: - MapParserStrategy protocol - -protocol MapParserStrategy { - func canHandle(line: String) -> Bool - func handle(index: Int, line: String, vertices: [String: Vertex]) -> (Any.Type, Any) -} - -struct AnyMapParserStrategy: MapParserStrategy { - - private let base: MapParserStrategy - - init<T: MapParserStrategy>(_ base: T) { - self.base = base - } - - func canHandle(line: String) -> Bool { - return base.canHandle(line: line) - } - func handle(index: Int, line: String, vertices: [String: Vertex]) -> (Any.Type, Any) { - return base.handle(index: index, line: line, vertices: vertices) - } -} - -// MARK: - Map Builder - -class MapBuilder { - var vertices: [String: Vertex] = [:] - private var edges: [MapEdge] = [] - private var inertias: [Inertia] = [] - private var opportunities: [Opportunity] = [] - private var notes: [Note] = [] - private var stages: [CGFloat] = defaultDimensions - private var groups: [[Vertex]] = [] - - func addObjectToMap(type: Any.Type, object: Any) { - if type == Vertex.self { - let vertex = object as! Vertex - vertices[vertex.label] = vertex - } - - if type == MapEdge.self { - let edge = object as! MapEdge - edges.append(edge) - } - - if type == Inertia.self { - let inertia = object as! Inertia - inertias.append(inertia) - } - - if type == Opportunity.self { - let opportunity = object as! Opportunity - opportunities.append(opportunity) - } - - if type == Note.self { - let note = object as! Note - notes.append(note) - } - - if type == StageDimensions.self { - let stageDimensions = object as! StageDimensions - stages[stageDimensions.index] = stageDimensions.dimensions - } - - if type == [Vertex].self { - let group = object as! [Vertex] - groups.append(group) - } - } - - func build() -> ParsedMap { - let mappedVertices = vertices.map { label, vertex in return vertex } - return ParsedMap( - vertices: mappedVertices, edges: edges, inertias: inertias, opportunities: opportunities, - notes: notes, - stages: stages, groups: groups) - } -} diff --git a/Map/Business/MapParser/Strategies/EdgeParserStrategy.swift b/Map/Business/MapParser/Strategies/EdgeParserStrategy.swift deleted file mode 100644 index cc29b49..0000000 --- a/Map/Business/MapParser/Strategies/EdgeParserStrategy.swift +++ /dev/null @@ -1,46 +0,0 @@ -// 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 Foundation - -struct EdgeParserStrategy: MapParserStrategy { - private let regex = MapParsingPatterns.edge - - func canHandle(line: String) -> Bool { - let range = NSRange(location: 0, length: line.utf16.count) - let matches = regex.matches(in: String(line), options: [], range: range) - return matches.count > 0 && matches[0].numberOfRanges == 4 - } - - func handle(index: Int, line: String, vertices: [String: Vertex]) -> (Any.Type, Any) { - let range = NSRange(location: 0, length: line.utf16.count) - let matches = regex.matches(in: String(line), options: [], range: range) - - let match = matches[0] - let arrowhead = String(line[Range(match.range(at: 2), in: line)!]) == ">" - let vertexA = String(line[Range(match.range(at: 1), in: line)!]) - let vertexB = String(line[Range(match.range(at: 3), in: line)!]) - - if let origin = vertices[vertexA] { - if let destination = vertices[vertexB] { - let edge = MapEdge( - id: index, origin: origin.position, destination: destination.position, - arrowhead: arrowhead) - return (MapEdge.self, edge) - } - } - - return (NSObject.self, NSObject()) // No matching object - } -} diff --git a/Map/Business/MapParser/Strategies/GroupParserStrategy.swift b/Map/Business/MapParser/Strategies/GroupParserStrategy.swift deleted file mode 100644 index 96de365..0000000 --- a/Map/Business/MapParser/Strategies/GroupParserStrategy.swift +++ /dev/null @@ -1,51 +0,0 @@ -// 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 Foundation - -struct GroupParserStrategy: MapParserStrategy { - private let regex = MapParsingPatterns.group - - func canHandle(line: String) -> Bool { - let range = NSRange(location: 0, length: line.utf16.count) - let matches = regex.matches(in: String(line), options: [], range: range) - return matches.count > 0 && matches[0].numberOfRanges == 3 - } - - func handle(index: Int, line: String, vertices: [String: Vertex]) -> (Any.Type, Any) { - let range = NSRange(location: 0, length: line.utf16.count) - let matches = regex.matches(in: String(line), options: [], range: range) - - let match = matches[0] - var groupVertices: [Vertex] = [] - let vertexIdString = String(line[Range(match.range(at: 2), in: line)!]) - let vertexIds = vertexIdString.split(separator: ",", omittingEmptySubsequences: true).map( - String.init - ).map({ vertexId in - vertexId.trimmingCharacters(in: .whitespacesAndNewlines) - }) - - for vertexId in vertexIds { - if let vertex = vertices[vertexId] { - groupVertices.append(vertex) - } - } - - if groupVertices.count > 0 { - return ([Vertex].self, groupVertices) - } - - return (NSObject.self, NSObject()) - } -} diff --git a/Map/Business/MapParser/Strategies/InertiaParserStrategy.swift b/Map/Business/MapParser/Strategies/InertiaParserStrategy.swift deleted file mode 100644 index d5d515e..0000000 --- a/Map/Business/MapParser/Strategies/InertiaParserStrategy.swift +++ /dev/null @@ -1,40 +0,0 @@ -// 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 Foundation - -struct InertiaParserStrategy: MapParserStrategy { - private let regex = MapParsingPatterns.inertia - - func canHandle(line: String) -> Bool { - let range = NSRange(location: 0, length: line.utf16.count) - let matches = regex.matches(in: String(line), options: [], range: range) - return matches.count > 0 && matches[0].numberOfRanges == 3 - } - - func handle(index: Int, line: String, vertices: [String: Vertex]) -> (Any.Type, Any) { - let range = NSRange(location: 0, length: line.utf16.count) - let matches = regex.matches(in: String(line), options: [], range: range) - - let match = matches[0] - let vertexA = String(line[Range(match.range(at: 2), in: line)!]) - - if let vertex = vertices[vertexA] { - let inertia = Inertia(id: index, position: vertex.position) - return (Inertia.self, inertia) - } - - return (NSObject.self, NSObject()) // No matching object - } -} diff --git a/Map/Business/MapParser/Strategies/NoteParserStrategy.swift b/Map/Business/MapParser/Strategies/NoteParserStrategy.swift deleted file mode 100644 index d06c768..0000000 --- a/Map/Business/MapParser/Strategies/NoteParserStrategy.swift +++ /dev/null @@ -1,45 +0,0 @@ -// 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 Foundation - -struct NoteParserStrategy: MapParserStrategy { - private let regex = MapParsingPatterns.note - - func canHandle(line: String) -> Bool { - let range = NSRange(location: 0, length: line.utf16.count) - let matches = regex.matches(in: String(line), options: [], range: range) - return matches.count > 0 && matches[0].numberOfRanges == 5 - } - - func handle(index: Int, line: String, vertices: [String: Vertex]) -> (Any.Type, Any) { - let range = NSRange(location: 0, length: line.utf16.count) - let matches = regex.matches(in: String(line), options: [], range: range) - - let match = matches[0] - let text = String(line[Range(match.range(at: 4), in: line)!]) - let xString = String(line[Range(match.range(at: 2), in: line)!]) - let yString = String(line[Range(match.range(at: 3), in: line)!]) - let x = CGFloat(truncating: NumberFormatter().number(from: xString) ?? 0.0) - let y = CGFloat(truncating: NumberFormatter().number(from: yString) ?? 0.0) - - let note = Note( - id: index, - position: CGPoint(x: x, y: y), - text: text - ) - - return (Note.self, note) - } -} diff --git a/Map/Business/MapParser/Strategies/OpportunityParserStrategy.swift b/Map/Business/MapParser/Strategies/OpportunityParserStrategy.swift deleted file mode 100644 index 25e6cc4..0000000 --- a/Map/Business/MapParser/Strategies/OpportunityParserStrategy.swift +++ /dev/null @@ -1,47 +0,0 @@ -// 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 Foundation - -struct OpportunityParserStrategy: MapParserStrategy { - private let regex = MapParsingPatterns.opportunity - - func canHandle(line: String) -> Bool { - let range = NSRange(location: 0, length: line.utf16.count) - let matches = regex.matches(in: String(line), options: [], range: range) - return matches.count > 0 && matches[0].numberOfRanges == 5 - } - - func handle(index: Int, line: String, vertices: [String: Vertex]) -> (Any.Type, Any) { - let range = NSRange(location: 0, length: line.utf16.count) - let matches = regex.matches(in: String(line), options: [], range: range) - - let match = matches[0] - let multiplier = CGFloat( - String(line[Range(match.range(at: 3), in: line)!]) == "-" ? -1.0 : 1.0) - let vertex = String(line[Range(match.range(at: 2), in: line)!]) - let opportunityString = String(line[Range(match.range(at: 4), in: line)!]) - let opportunity = CGFloat( - truncating: NumberFormatter().number(from: opportunityString) ?? 0.0) - - if let origin = vertices[vertex] { - let destination = CGPoint( - x: origin.position.x + opportunity * multiplier, y: origin.position.y) - let opportunity = Opportunity(id: index, origin: origin.position, destination: destination) - return (Opportunity.self, opportunity) - } - - return (NSObject.self, NSObject()) // No matching object - } -} diff --git a/Map/Business/MapParser/Strategies/StageParserStrategy.swift b/Map/Business/MapParser/Strategies/StageParserStrategy.swift deleted file mode 100644 index 6e4a341..0000000 --- a/Map/Business/MapParser/Strategies/StageParserStrategy.swift +++ /dev/null @@ -1,38 +0,0 @@ -// 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 Foundation - -struct StageParserStrategy: MapParserStrategy { - private let regex = MapParsingPatterns.stage - - func canHandle(line: String) -> Bool { - let range = NSRange(location: 0, length: line.utf16.count) - let matches = regex.matches(in: String(line), options: [], range: range) - return matches.count > 0 && matches[0].numberOfRanges == 3 - } - - func handle(index: Int, line: String, vertices: [String: Vertex]) -> (Any.Type, Any) { - let range = NSRange(location: 0, length: line.utf16.count) - let matches = regex.matches(in: String(line), options: [], range: range) - - let match = matches[0] - let stage = String(line[Range(match.range(at: 1), in: line)!]) - let dimensionsString = String(line[Range(match.range(at: 2), in: line)!]) - let dimensions = CGFloat(truncating: NumberFormatter().number(from: dimensionsString) ?? 0.0) - - let stageDimensions = StageDimensions(index: stage.count - 1, dimensions: dimensions) - return (StageDimensions.self, stageDimensions) - } -} diff --git a/Map/Business/MapParser/Strategies/VertexParserStrategy.swift b/Map/Business/MapParser/Strategies/VertexParserStrategy.swift deleted file mode 100644 index 0ca6fc1..0000000 --- a/Map/Business/MapParser/Strategies/VertexParserStrategy.swift +++ /dev/null @@ -1,50 +0,0 @@ -// 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 Foundation - -struct VertexParserStrategy: MapParserStrategy { - private let regex = MapParsingPatterns.vertex - - func canHandle(line: String) -> Bool { - let range = NSRange(location: 0, length: line.utf16.count) - let matches = regex.matches(in: String(line), options: [], range: range) - return matches.count > 0 && matches[0].numberOfRanges >= 4 - } - - func handle(index: Int, line: String, vertices: [String: Vertex]) -> (Any.Type, Any) { - let range = NSRange(location: 0, length: line.utf16.count) - let matches = regex.matches(in: String(line), options: [], range: range) - - let match = matches[0] - let key = String(line[Range(match.range(at: 1), in: line)!]) - let xString = String(line[Range(match.range(at: 2), in: line)!]) - let yString = String(line[Range(match.range(at: 3), in: line)!]) - let x = CGFloat(truncating: NumberFormatter().number(from: xString) ?? 0.0) - let y = CGFloat(truncating: NumberFormatter().number(from: yString) ?? 0.0) - - var vertex = Vertex( - id: index, - label: key, - position: CGPoint(x: x, y: y) - ) - - if let range = Range(match.range(at: 4), in: line) { - let shapeString = String(line[range]) - vertex.shape = VertexShape(rawValue: shapeString.lowercased()) ?? .circle - } - - return (Vertex.self, vertex) - } -} diff --git a/Map/Business/RenderableMap.swift b/Map/Business/RenderableMap.swift new file mode 100644 index 0000000..c8ab776 --- /dev/null +++ b/Map/Business/RenderableMap.swift @@ -0,0 +1,210 @@ +// 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 Foundation + +struct RenderableMap { + let vertices: [Vertex] + let edges: [MapEdge] + let inertias: [Inertia] + let opportunities: [Opportunity] + let notes: [Note] + let stages: [CGFloat] + let groups: [[Vertex]] + + static let empty: RenderableMap = RenderableMap( + vertices: [], edges: [], inertias: [], opportunities: [], notes: [], stages: defaultDimensions, + groups: []) + + static func from(_ entities: [Wmap.Entity]) -> RenderableMap { + var vertices: [Vertex] = [] + var edges: [MapEdge] = [] + var inertias: [Inertia] = [] + var opportunities: [Opportunity] = [] + var notes: [Note] = [] + var stages: [CGFloat] = [25.0, 50.0, 75.0] // default dimensions + var groups: [[Vertex]] = [] + + // Build a vertex lookup for edges, groups, etc. + var vertexMap: [String: Vertex] = [:] + var vertexIdCounter = 0 + + for entity in entities { + switch entity { + case .vertex(let vertex): + let newVertex = Vertex( + id: vertexIdCounter, + label: vertex.label, + position: CGPoint(x: vertex.coordinates.0, y: vertex.coordinates.1), + shape: convertShape(vertex.shape) + ) + vertices.append(newVertex) + vertexMap[vertex.label] = newVertex + vertexIdCounter += 1 + + case .note(let note): + notes.append( + Note( + id: notes.count, + position: CGPoint(x: note.coordinates.0, y: note.coordinates.1), + text: note.text + )) + + case .stage(let stage): + let stageIndex = convertStageNumber(stage.number) - 1 + if stageIndex >= 0 && stageIndex < 3 { + stages[stageIndex] = stage.value + } + + default: + continue + } + } + + // Second pass for entities that depend on vertices + for entity in entities { + switch entity { + case .edge(let edge): + if let fromVertex = vertexMap[edge.from], + let toVertex = vertexMap[edge.to] + { + edges.append( + MapEdge( + id: edges.count, + origin: fromVertex.position, + destination: toVertex.position, + arrowhead: edge.isDirected + )) + } + + case .inertia(let inertia): + if let vertex = vertexMap[inertia.vertex] { + inertias.append( + Inertia( + id: inertias.count, + position: vertex.position + )) + } + + case .evolution(let evolution): + if let vertex = vertexMap[evolution.vertex] { + let direction: CGFloat = evolution.isPositive ? 1.0 : -1.0 + let destination = CGPoint( + x: vertex.position.x + (direction * evolution.value), + y: vertex.position.y + ) + opportunities.append( + Opportunity( + id: opportunities.count, + origin: vertex.position, + destination: destination + )) + } + + case .group(let group): + var groupVertices: [Vertex] = [] + for vertexName in group.vertices { + if let vertex = vertexMap[vertexName] { + groupVertices.append(vertex) + } + } + if !groupVertices.isEmpty { + groups.append(groupVertices) + } + + default: + continue + } + } + + return RenderableMap( + vertices: vertices, + edges: edges, + inertias: inertias, + opportunities: opportunities, + notes: notes, + stages: stages, + groups: groups + ) + } + + private static func convertShape(_ shape: String?) -> VertexShape { + guard let shape = shape?.lowercased() else { return .circle } + switch shape { + case "square": return .square + case "triangle": return .triangle + case "x": return .x + default: return .circle + } + } + + private static func convertStageNumber(_ stageNumber: String) -> Int { + switch stageNumber.lowercased() { + case "i": return 1 + case "ii": return 2 + case "iii": return 3 + case "iv": return 4 + default: return 1 + } + } +} + +struct Vertex: Identifiable, Hashable { + let id: Int + let label: String + let position: CGPoint + var shape: VertexShape = .circle +} + +struct Note { + let id: Int + let position: CGPoint + let text: String +} + +enum VertexShape: String { + case circle + case square + case triangle + case x +} + +struct MapEdge { + let id: Int + let origin: CGPoint + let destination: CGPoint + let arrowhead: Bool +} + +struct Inertia { + let id: Int + let position: CGPoint +} + +struct Opportunity { + let id: Int + let origin: CGPoint + let destination: CGPoint +} + +struct StageDimensions { + let index: Int + let dimensions: CGFloat +} + +private let defaultDimensions: [CGFloat] = [ + 25.0, + 50.0, + 75.0, +] diff --git a/Map/Presentation/Complex Components/MapRender/MapRenderView.swift b/Map/Presentation/Complex Components/MapRender/MapRenderView.swift index 08d1d61..fe98ace 100644 --- a/Map/Presentation/Complex Components/MapRender/MapRenderView.swift +++ b/Map/Presentation/Complex Components/MapRender/MapRenderView.swift @@ -34,7 +34,7 @@ struct MapRenderView: View { Stage.stages(evolution) } - @State private var parsedMap: ParsedMap = ParsedMap.empty + @State private var parsedMap: RenderableMap = RenderableMap.empty @State private var smartLabelPositions: [Int: CGPoint] = [:] @State private var parseTask: Task<Void, Never>? @@ -53,7 +53,7 @@ struct MapRenderView: View { parseTask = Task { let parsed = await Task.detached(priority: .userInitiated) { - convertEntitiesToParsedMap(currentEntities) + RenderableMap.from(currentEntities) }.value let positions = await Task.detached(priority: .userInitiated) { @@ -94,138 +94,6 @@ struct MapRenderView: View { } } - private nonisolated func convertEntitiesToParsedMap(_ entities: [Wmap.Entity]) -> ParsedMap { - var vertices: [Vertex] = [] - var edges: [MapEdge] = [] - var inertias: [Inertia] = [] - var opportunities: [Opportunity] = [] - var notes: [Note] = [] - var stages: [CGFloat] = [25.0, 50.0, 75.0] // default dimensions - var groups: [[Vertex]] = [] - - // Build a vertex lookup for edges, groups, etc. - var vertexMap: [String: Vertex] = [:] - var vertexIdCounter = 0 - - for entity in entities { - switch entity { - case .vertex(let vertex): - let newVertex = Vertex( - id: vertexIdCounter, - label: vertex.label, - position: CGPoint(x: vertex.coordinates.0, y: vertex.coordinates.1), - shape: convertShape(vertex.shape) - ) - vertices.append(newVertex) - vertexMap[vertex.label] = newVertex - vertexIdCounter += 1 - - case .note(let note): - notes.append( - Note( - id: notes.count, - position: CGPoint(x: note.coordinates.0, y: note.coordinates.1), - text: note.text - )) - - case .stage(let stage): - let stageIndex = convertStageNumber(stage.number) - 1 - if stageIndex >= 0 && stageIndex < 3 { - stages[stageIndex] = stage.value - } - - default: - continue - } - } - - // Second pass for entities that depend on vertices - for entity in entities { - switch entity { - case .edge(let edge): - if let fromVertex = vertexMap[edge.from], - let toVertex = vertexMap[edge.to] - { - edges.append( - MapEdge( - id: edges.count, - origin: fromVertex.position, - destination: toVertex.position, - arrowhead: edge.isDirected - )) - } - - case .inertia(let inertia): - if let vertex = vertexMap[inertia.vertex] { - inertias.append( - Inertia( - id: inertias.count, - position: vertex.position - )) - } - - case .evolution(let evolution): - if let vertex = vertexMap[evolution.vertex] { - let direction: CGFloat = evolution.isPositive ? 1.0 : -1.0 - let destination = CGPoint( - x: vertex.position.x + (direction * evolution.value), - y: vertex.position.y - ) - opportunities.append( - Opportunity( - id: opportunities.count, - origin: vertex.position, - destination: destination - )) - } - - case .group(let group): - var groupVertices: [Vertex] = [] - for vertexName in group.vertices { - if let vertex = vertexMap[vertexName] { - groupVertices.append(vertex) - } - } - if !groupVertices.isEmpty { - groups.append(groupVertices) - } - - default: - continue - } - } - - return ParsedMap( - vertices: vertices, - edges: edges, - inertias: inertias, - opportunities: opportunities, - notes: notes, - stages: stages, - groups: groups - ) - } - - private nonisolated func convertShape(_ shape: String?) -> VertexShape { - guard let shape = shape?.lowercased() else { return .circle } - switch shape { - case "square": return .square - case "triangle": return .triangle - case "x": return .x - default: return .circle - } - } - - private nonisolated func convertStageNumber(_ stageNumber: String) -> Int { - switch stageNumber.lowercased() { - case "i": return 1 - case "ii": return 2 - case "iii": return 3 - case "iv": return 4 - default: return 1 - } - } - var body: some View { ZStack(alignment: .topLeading) { |