├── .gitignore ├── LICENSE ├── README.md ├── Screenshot1.png ├── Screenshot2.png ├── mindmap-swiftui.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── mindmap-swiftui ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── 100.png │ │ ├── 144.png │ │ ├── 152.png │ │ ├── 167.png │ │ ├── 20.png │ │ ├── 29.png │ │ ├── 40.png │ │ ├── 50.png │ │ ├── 58.png │ │ ├── 72.png │ │ ├── 76.png │ │ ├── 80.png │ │ ├── Contents.json │ │ └── appstore.png │ └── Contents.json ├── FilesListView.swift ├── Help │ └── CGPoint+Help.swift ├── Info.plist ├── Model │ ├── Edge.swift │ ├── Mesh+Demo.swift │ ├── Mesh+MathHelp.swift │ ├── Mesh.swift │ ├── Node.swift │ └── SelectionHandler.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── View Stack │ ├── BoringListView.swift │ ├── EdgeMapView.swift │ ├── EdgeView.swift │ ├── MapView.swift │ ├── NodeMapView.swift │ ├── NodeView.swift │ └── SurfaceView.swift └── mindmap_swiftuiApp.swift ├── mindmap-swiftuiTests └── mindmap_swiftuiTests.swift └── mindmap-swiftuiUITests ├── mindmap_swiftuiUITests.swift └── mindmap_swiftuiUITestsLaunchTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Andrey Bashta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mindmap-swiftui 2 | A mind map or map of memory, mind, associative map- a diagram that displays words, ideas, tasks, or other elements located radially around the main word or idea 3 | 4 | ![Screenshoot1](https://raw.github.com/andreybashta/mindmap-swiftui/master/Screenshot1.png) 5 | ![Screenshoot2](https://raw.github.com/andreybashta/mindmap-swiftui/master/Screenshot2.png) 6 | 7 | Based on https://www.raywenderlich.com/7705231-creating-a-mind-map-ui-in-swiftui 8 | -------------------------------------------------------------------------------- /Screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andriybashta/mindmap-swiftui/b1f6174aee65750aa0fd664a4e95d2f9fa483db5/Screenshot1.png -------------------------------------------------------------------------------- /Screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andriybashta/mindmap-swiftui/b1f6174aee65750aa0fd664a4e95d2f9fa483db5/Screenshot2.png -------------------------------------------------------------------------------- /mindmap-swiftui.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 55; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | AB4577E12740972E00E7F889 /* mindmap_swiftuiApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB4577E02740972E00E7F889 /* mindmap_swiftuiApp.swift */; }; 11 | AB4577E52740973200E7F889 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AB4577E42740973200E7F889 /* Assets.xcassets */; }; 12 | AB4577E82740973200E7F889 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AB4577E72740973200E7F889 /* Preview Assets.xcassets */; }; 13 | AB4577F22740973200E7F889 /* mindmap_swiftuiTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB4577F12740973200E7F889 /* mindmap_swiftuiTests.swift */; }; 14 | AB4577FC2740973200E7F889 /* mindmap_swiftuiUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB4577FB2740973200E7F889 /* mindmap_swiftuiUITests.swift */; }; 15 | AB4577FE2740973200E7F889 /* mindmap_swiftuiUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB4577FD2740973200E7F889 /* mindmap_swiftuiUITestsLaunchTests.swift */; }; 16 | AB45781A27412FBE00E7F889 /* Mesh.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB45780C27412FBE00E7F889 /* Mesh.swift */; }; 17 | AB45781B27412FBE00E7F889 /* SelectionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB45780D27412FBE00E7F889 /* SelectionHandler.swift */; }; 18 | AB45781C27412FBE00E7F889 /* Edge.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB45780E27412FBE00E7F889 /* Edge.swift */; }; 19 | AB45781D27412FBE00E7F889 /* Mesh+Demo.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB45780F27412FBE00E7F889 /* Mesh+Demo.swift */; }; 20 | AB45781E27412FBE00E7F889 /* Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB45781027412FBE00E7F889 /* Node.swift */; }; 21 | AB45781F27412FBE00E7F889 /* Mesh+MathHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB45781127412FBE00E7F889 /* Mesh+MathHelp.swift */; }; 22 | AB45782027412FBE00E7F889 /* EdgeMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB45781327412FBE00E7F889 /* EdgeMapView.swift */; }; 23 | AB45782127412FBE00E7F889 /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB45781427412FBE00E7F889 /* MapView.swift */; }; 24 | AB45782227412FBE00E7F889 /* EdgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB45781527412FBE00E7F889 /* EdgeView.swift */; }; 25 | AB45782327412FBE00E7F889 /* NodeMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB45781627412FBE00E7F889 /* NodeMapView.swift */; }; 26 | AB45782427412FBE00E7F889 /* SurfaceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB45781727412FBE00E7F889 /* SurfaceView.swift */; }; 27 | AB45782527412FBE00E7F889 /* NodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB45781827412FBE00E7F889 /* NodeView.swift */; }; 28 | AB45782627412FBE00E7F889 /* BoringListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB45781927412FBE00E7F889 /* BoringListView.swift */; }; 29 | AB4578292741303200E7F889 /* CGPoint+Help.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB4578282741303200E7F889 /* CGPoint+Help.swift */; }; 30 | AB4DCB5B2741A078000AB777 /* FilesListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB4DCB5A2741A078000AB777 /* FilesListView.swift */; }; 31 | /* End PBXBuildFile section */ 32 | 33 | /* Begin PBXContainerItemProxy section */ 34 | AB4577EE2740973200E7F889 /* PBXContainerItemProxy */ = { 35 | isa = PBXContainerItemProxy; 36 | containerPortal = AB4577D52740972E00E7F889 /* Project object */; 37 | proxyType = 1; 38 | remoteGlobalIDString = AB4577DC2740972E00E7F889; 39 | remoteInfo = "mindmap-swiftui"; 40 | }; 41 | AB4577F82740973200E7F889 /* PBXContainerItemProxy */ = { 42 | isa = PBXContainerItemProxy; 43 | containerPortal = AB4577D52740972E00E7F889 /* Project object */; 44 | proxyType = 1; 45 | remoteGlobalIDString = AB4577DC2740972E00E7F889; 46 | remoteInfo = "mindmap-swiftui"; 47 | }; 48 | /* End PBXContainerItemProxy section */ 49 | 50 | /* Begin PBXFileReference section */ 51 | AB4577DD2740972E00E7F889 /* mindmap-swiftui.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "mindmap-swiftui.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | AB4577E02740972E00E7F889 /* mindmap_swiftuiApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = mindmap_swiftuiApp.swift; sourceTree = ""; }; 53 | AB4577E42740973200E7F889 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 54 | AB4577E72740973200E7F889 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 55 | AB4577ED2740973200E7F889 /* mindmap-swiftuiTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "mindmap-swiftuiTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 56 | AB4577F12740973200E7F889 /* mindmap_swiftuiTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = mindmap_swiftuiTests.swift; sourceTree = ""; }; 57 | AB4577F72740973200E7F889 /* mindmap-swiftuiUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "mindmap-swiftuiUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | AB4577FB2740973200E7F889 /* mindmap_swiftuiUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = mindmap_swiftuiUITests.swift; sourceTree = ""; }; 59 | AB4577FD2740973200E7F889 /* mindmap_swiftuiUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = mindmap_swiftuiUITestsLaunchTests.swift; sourceTree = ""; }; 60 | AB45780A27412F6D00E7F889 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 61 | AB45780C27412FBE00E7F889 /* Mesh.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Mesh.swift; sourceTree = ""; }; 62 | AB45780D27412FBE00E7F889 /* SelectionHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectionHandler.swift; sourceTree = ""; }; 63 | AB45780E27412FBE00E7F889 /* Edge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Edge.swift; sourceTree = ""; }; 64 | AB45780F27412FBE00E7F889 /* Mesh+Demo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Mesh+Demo.swift"; sourceTree = ""; }; 65 | AB45781027412FBE00E7F889 /* Node.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Node.swift; sourceTree = ""; }; 66 | AB45781127412FBE00E7F889 /* Mesh+MathHelp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Mesh+MathHelp.swift"; sourceTree = ""; }; 67 | AB45781327412FBE00E7F889 /* EdgeMapView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EdgeMapView.swift; sourceTree = ""; }; 68 | AB45781427412FBE00E7F889 /* MapView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapView.swift; sourceTree = ""; }; 69 | AB45781527412FBE00E7F889 /* EdgeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EdgeView.swift; sourceTree = ""; }; 70 | AB45781627412FBE00E7F889 /* NodeMapView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NodeMapView.swift; sourceTree = ""; }; 71 | AB45781727412FBE00E7F889 /* SurfaceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SurfaceView.swift; sourceTree = ""; }; 72 | AB45781827412FBE00E7F889 /* NodeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NodeView.swift; sourceTree = ""; }; 73 | AB45781927412FBE00E7F889 /* BoringListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoringListView.swift; sourceTree = ""; }; 74 | AB4578282741303200E7F889 /* CGPoint+Help.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGPoint+Help.swift"; sourceTree = ""; }; 75 | AB4DCB5A2741A078000AB777 /* FilesListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilesListView.swift; sourceTree = ""; }; 76 | /* End PBXFileReference section */ 77 | 78 | /* Begin PBXFrameworksBuildPhase section */ 79 | AB4577DA2740972E00E7F889 /* Frameworks */ = { 80 | isa = PBXFrameworksBuildPhase; 81 | buildActionMask = 2147483647; 82 | files = ( 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | AB4577EA2740973200E7F889 /* Frameworks */ = { 87 | isa = PBXFrameworksBuildPhase; 88 | buildActionMask = 2147483647; 89 | files = ( 90 | ); 91 | runOnlyForDeploymentPostprocessing = 0; 92 | }; 93 | AB4577F42740973200E7F889 /* Frameworks */ = { 94 | isa = PBXFrameworksBuildPhase; 95 | buildActionMask = 2147483647; 96 | files = ( 97 | ); 98 | runOnlyForDeploymentPostprocessing = 0; 99 | }; 100 | /* End PBXFrameworksBuildPhase section */ 101 | 102 | /* Begin PBXGroup section */ 103 | AB4577D42740972E00E7F889 = { 104 | isa = PBXGroup; 105 | children = ( 106 | AB4577DF2740972E00E7F889 /* mindmap-swiftui */, 107 | AB4577F02740973200E7F889 /* mindmap-swiftuiTests */, 108 | AB4577FA2740973200E7F889 /* mindmap-swiftuiUITests */, 109 | AB4577DE2740972E00E7F889 /* Products */, 110 | ); 111 | sourceTree = ""; 112 | }; 113 | AB4577DE2740972E00E7F889 /* Products */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | AB4577DD2740972E00E7F889 /* mindmap-swiftui.app */, 117 | AB4577ED2740973200E7F889 /* mindmap-swiftuiTests.xctest */, 118 | AB4577F72740973200E7F889 /* mindmap-swiftuiUITests.xctest */, 119 | ); 120 | name = Products; 121 | sourceTree = ""; 122 | }; 123 | AB4577DF2740972E00E7F889 /* mindmap-swiftui */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | AB45780A27412F6D00E7F889 /* Info.plist */, 127 | AB4577E02740972E00E7F889 /* mindmap_swiftuiApp.swift */, 128 | AB4DCB5A2741A078000AB777 /* FilesListView.swift */, 129 | AB4577E42740973200E7F889 /* Assets.xcassets */, 130 | AB4578272741303200E7F889 /* Help */, 131 | AB45780B27412FBE00E7F889 /* Model */, 132 | AB45781227412FBE00E7F889 /* View Stack */, 133 | AB4577E62740973200E7F889 /* Preview Content */, 134 | ); 135 | path = "mindmap-swiftui"; 136 | sourceTree = ""; 137 | }; 138 | AB4577E62740973200E7F889 /* Preview Content */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | AB4577E72740973200E7F889 /* Preview Assets.xcassets */, 142 | ); 143 | path = "Preview Content"; 144 | sourceTree = ""; 145 | }; 146 | AB4577F02740973200E7F889 /* mindmap-swiftuiTests */ = { 147 | isa = PBXGroup; 148 | children = ( 149 | AB4577F12740973200E7F889 /* mindmap_swiftuiTests.swift */, 150 | ); 151 | path = "mindmap-swiftuiTests"; 152 | sourceTree = ""; 153 | }; 154 | AB4577FA2740973200E7F889 /* mindmap-swiftuiUITests */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | AB4577FB2740973200E7F889 /* mindmap_swiftuiUITests.swift */, 158 | AB4577FD2740973200E7F889 /* mindmap_swiftuiUITestsLaunchTests.swift */, 159 | ); 160 | path = "mindmap-swiftuiUITests"; 161 | sourceTree = ""; 162 | }; 163 | AB45780B27412FBE00E7F889 /* Model */ = { 164 | isa = PBXGroup; 165 | children = ( 166 | AB45780C27412FBE00E7F889 /* Mesh.swift */, 167 | AB45780D27412FBE00E7F889 /* SelectionHandler.swift */, 168 | AB45780E27412FBE00E7F889 /* Edge.swift */, 169 | AB45780F27412FBE00E7F889 /* Mesh+Demo.swift */, 170 | AB45781027412FBE00E7F889 /* Node.swift */, 171 | AB45781127412FBE00E7F889 /* Mesh+MathHelp.swift */, 172 | ); 173 | path = Model; 174 | sourceTree = ""; 175 | }; 176 | AB45781227412FBE00E7F889 /* View Stack */ = { 177 | isa = PBXGroup; 178 | children = ( 179 | AB45781327412FBE00E7F889 /* EdgeMapView.swift */, 180 | AB45781427412FBE00E7F889 /* MapView.swift */, 181 | AB45781527412FBE00E7F889 /* EdgeView.swift */, 182 | AB45781627412FBE00E7F889 /* NodeMapView.swift */, 183 | AB45781727412FBE00E7F889 /* SurfaceView.swift */, 184 | AB45781827412FBE00E7F889 /* NodeView.swift */, 185 | AB45781927412FBE00E7F889 /* BoringListView.swift */, 186 | ); 187 | path = "View Stack"; 188 | sourceTree = ""; 189 | }; 190 | AB4578272741303200E7F889 /* Help */ = { 191 | isa = PBXGroup; 192 | children = ( 193 | AB4578282741303200E7F889 /* CGPoint+Help.swift */, 194 | ); 195 | path = Help; 196 | sourceTree = ""; 197 | }; 198 | /* End PBXGroup section */ 199 | 200 | /* Begin PBXNativeTarget section */ 201 | AB4577DC2740972E00E7F889 /* mindmap-swiftui */ = { 202 | isa = PBXNativeTarget; 203 | buildConfigurationList = AB4578012740973200E7F889 /* Build configuration list for PBXNativeTarget "mindmap-swiftui" */; 204 | buildPhases = ( 205 | AB4577D92740972E00E7F889 /* Sources */, 206 | AB4577DA2740972E00E7F889 /* Frameworks */, 207 | AB4577DB2740972E00E7F889 /* Resources */, 208 | ); 209 | buildRules = ( 210 | ); 211 | dependencies = ( 212 | ); 213 | name = "mindmap-swiftui"; 214 | productName = "mindmap-swiftui"; 215 | productReference = AB4577DD2740972E00E7F889 /* mindmap-swiftui.app */; 216 | productType = "com.apple.product-type.application"; 217 | }; 218 | AB4577EC2740973200E7F889 /* mindmap-swiftuiTests */ = { 219 | isa = PBXNativeTarget; 220 | buildConfigurationList = AB4578042740973200E7F889 /* Build configuration list for PBXNativeTarget "mindmap-swiftuiTests" */; 221 | buildPhases = ( 222 | AB4577E92740973200E7F889 /* Sources */, 223 | AB4577EA2740973200E7F889 /* Frameworks */, 224 | AB4577EB2740973200E7F889 /* Resources */, 225 | ); 226 | buildRules = ( 227 | ); 228 | dependencies = ( 229 | AB4577EF2740973200E7F889 /* PBXTargetDependency */, 230 | ); 231 | name = "mindmap-swiftuiTests"; 232 | productName = "mindmap-swiftuiTests"; 233 | productReference = AB4577ED2740973200E7F889 /* mindmap-swiftuiTests.xctest */; 234 | productType = "com.apple.product-type.bundle.unit-test"; 235 | }; 236 | AB4577F62740973200E7F889 /* mindmap-swiftuiUITests */ = { 237 | isa = PBXNativeTarget; 238 | buildConfigurationList = AB4578072740973200E7F889 /* Build configuration list for PBXNativeTarget "mindmap-swiftuiUITests" */; 239 | buildPhases = ( 240 | AB4577F32740973200E7F889 /* Sources */, 241 | AB4577F42740973200E7F889 /* Frameworks */, 242 | AB4577F52740973200E7F889 /* Resources */, 243 | ); 244 | buildRules = ( 245 | ); 246 | dependencies = ( 247 | AB4577F92740973200E7F889 /* PBXTargetDependency */, 248 | ); 249 | name = "mindmap-swiftuiUITests"; 250 | productName = "mindmap-swiftuiUITests"; 251 | productReference = AB4577F72740973200E7F889 /* mindmap-swiftuiUITests.xctest */; 252 | productType = "com.apple.product-type.bundle.ui-testing"; 253 | }; 254 | /* End PBXNativeTarget section */ 255 | 256 | /* Begin PBXProject section */ 257 | AB4577D52740972E00E7F889 /* Project object */ = { 258 | isa = PBXProject; 259 | attributes = { 260 | BuildIndependentTargetsInParallel = 1; 261 | LastSwiftUpdateCheck = 1310; 262 | LastUpgradeCheck = 1310; 263 | TargetAttributes = { 264 | AB4577DC2740972E00E7F889 = { 265 | CreatedOnToolsVersion = 13.1; 266 | }; 267 | AB4577EC2740973200E7F889 = { 268 | CreatedOnToolsVersion = 13.1; 269 | TestTargetID = AB4577DC2740972E00E7F889; 270 | }; 271 | AB4577F62740973200E7F889 = { 272 | CreatedOnToolsVersion = 13.1; 273 | TestTargetID = AB4577DC2740972E00E7F889; 274 | }; 275 | }; 276 | }; 277 | buildConfigurationList = AB4577D82740972E00E7F889 /* Build configuration list for PBXProject "mindmap-swiftui" */; 278 | compatibilityVersion = "Xcode 13.0"; 279 | developmentRegion = en; 280 | hasScannedForEncodings = 0; 281 | knownRegions = ( 282 | en, 283 | Base, 284 | ); 285 | mainGroup = AB4577D42740972E00E7F889; 286 | productRefGroup = AB4577DE2740972E00E7F889 /* Products */; 287 | projectDirPath = ""; 288 | projectRoot = ""; 289 | targets = ( 290 | AB4577DC2740972E00E7F889 /* mindmap-swiftui */, 291 | AB4577EC2740973200E7F889 /* mindmap-swiftuiTests */, 292 | AB4577F62740973200E7F889 /* mindmap-swiftuiUITests */, 293 | ); 294 | }; 295 | /* End PBXProject section */ 296 | 297 | /* Begin PBXResourcesBuildPhase section */ 298 | AB4577DB2740972E00E7F889 /* Resources */ = { 299 | isa = PBXResourcesBuildPhase; 300 | buildActionMask = 2147483647; 301 | files = ( 302 | AB4577E82740973200E7F889 /* Preview Assets.xcassets in Resources */, 303 | AB4577E52740973200E7F889 /* Assets.xcassets in Resources */, 304 | ); 305 | runOnlyForDeploymentPostprocessing = 0; 306 | }; 307 | AB4577EB2740973200E7F889 /* Resources */ = { 308 | isa = PBXResourcesBuildPhase; 309 | buildActionMask = 2147483647; 310 | files = ( 311 | ); 312 | runOnlyForDeploymentPostprocessing = 0; 313 | }; 314 | AB4577F52740973200E7F889 /* Resources */ = { 315 | isa = PBXResourcesBuildPhase; 316 | buildActionMask = 2147483647; 317 | files = ( 318 | ); 319 | runOnlyForDeploymentPostprocessing = 0; 320 | }; 321 | /* End PBXResourcesBuildPhase section */ 322 | 323 | /* Begin PBXSourcesBuildPhase section */ 324 | AB4577D92740972E00E7F889 /* Sources */ = { 325 | isa = PBXSourcesBuildPhase; 326 | buildActionMask = 2147483647; 327 | files = ( 328 | AB45781A27412FBE00E7F889 /* Mesh.swift in Sources */, 329 | AB45782227412FBE00E7F889 /* EdgeView.swift in Sources */, 330 | AB4578292741303200E7F889 /* CGPoint+Help.swift in Sources */, 331 | AB45782427412FBE00E7F889 /* SurfaceView.swift in Sources */, 332 | AB45782027412FBE00E7F889 /* EdgeMapView.swift in Sources */, 333 | AB45781F27412FBE00E7F889 /* Mesh+MathHelp.swift in Sources */, 334 | AB45781B27412FBE00E7F889 /* SelectionHandler.swift in Sources */, 335 | AB45781D27412FBE00E7F889 /* Mesh+Demo.swift in Sources */, 336 | AB4DCB5B2741A078000AB777 /* FilesListView.swift in Sources */, 337 | AB45781C27412FBE00E7F889 /* Edge.swift in Sources */, 338 | AB45782127412FBE00E7F889 /* MapView.swift in Sources */, 339 | AB45782627412FBE00E7F889 /* BoringListView.swift in Sources */, 340 | AB4577E12740972E00E7F889 /* mindmap_swiftuiApp.swift in Sources */, 341 | AB45781E27412FBE00E7F889 /* Node.swift in Sources */, 342 | AB45782527412FBE00E7F889 /* NodeView.swift in Sources */, 343 | AB45782327412FBE00E7F889 /* NodeMapView.swift in Sources */, 344 | ); 345 | runOnlyForDeploymentPostprocessing = 0; 346 | }; 347 | AB4577E92740973200E7F889 /* Sources */ = { 348 | isa = PBXSourcesBuildPhase; 349 | buildActionMask = 2147483647; 350 | files = ( 351 | AB4577F22740973200E7F889 /* mindmap_swiftuiTests.swift in Sources */, 352 | ); 353 | runOnlyForDeploymentPostprocessing = 0; 354 | }; 355 | AB4577F32740973200E7F889 /* Sources */ = { 356 | isa = PBXSourcesBuildPhase; 357 | buildActionMask = 2147483647; 358 | files = ( 359 | AB4577FC2740973200E7F889 /* mindmap_swiftuiUITests.swift in Sources */, 360 | AB4577FE2740973200E7F889 /* mindmap_swiftuiUITestsLaunchTests.swift in Sources */, 361 | ); 362 | runOnlyForDeploymentPostprocessing = 0; 363 | }; 364 | /* End PBXSourcesBuildPhase section */ 365 | 366 | /* Begin PBXTargetDependency section */ 367 | AB4577EF2740973200E7F889 /* PBXTargetDependency */ = { 368 | isa = PBXTargetDependency; 369 | target = AB4577DC2740972E00E7F889 /* mindmap-swiftui */; 370 | targetProxy = AB4577EE2740973200E7F889 /* PBXContainerItemProxy */; 371 | }; 372 | AB4577F92740973200E7F889 /* PBXTargetDependency */ = { 373 | isa = PBXTargetDependency; 374 | target = AB4577DC2740972E00E7F889 /* mindmap-swiftui */; 375 | targetProxy = AB4577F82740973200E7F889 /* PBXContainerItemProxy */; 376 | }; 377 | /* End PBXTargetDependency section */ 378 | 379 | /* Begin XCBuildConfiguration section */ 380 | AB4577FF2740973200E7F889 /* Debug */ = { 381 | isa = XCBuildConfiguration; 382 | buildSettings = { 383 | ALWAYS_SEARCH_USER_PATHS = NO; 384 | CLANG_ANALYZER_NONNULL = YES; 385 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 386 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 387 | CLANG_CXX_LIBRARY = "libc++"; 388 | CLANG_ENABLE_MODULES = YES; 389 | CLANG_ENABLE_OBJC_ARC = YES; 390 | CLANG_ENABLE_OBJC_WEAK = YES; 391 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 392 | CLANG_WARN_BOOL_CONVERSION = YES; 393 | CLANG_WARN_COMMA = YES; 394 | CLANG_WARN_CONSTANT_CONVERSION = YES; 395 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 396 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 397 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 398 | CLANG_WARN_EMPTY_BODY = YES; 399 | CLANG_WARN_ENUM_CONVERSION = YES; 400 | CLANG_WARN_INFINITE_RECURSION = YES; 401 | CLANG_WARN_INT_CONVERSION = YES; 402 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 403 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 404 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 405 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 406 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 407 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 408 | CLANG_WARN_STRICT_PROTOTYPES = YES; 409 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 410 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 411 | CLANG_WARN_UNREACHABLE_CODE = YES; 412 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 413 | COPY_PHASE_STRIP = NO; 414 | DEBUG_INFORMATION_FORMAT = dwarf; 415 | ENABLE_STRICT_OBJC_MSGSEND = YES; 416 | ENABLE_TESTABILITY = YES; 417 | GCC_C_LANGUAGE_STANDARD = gnu11; 418 | GCC_DYNAMIC_NO_PIC = NO; 419 | GCC_NO_COMMON_BLOCKS = YES; 420 | GCC_OPTIMIZATION_LEVEL = 0; 421 | GCC_PREPROCESSOR_DEFINITIONS = ( 422 | "DEBUG=1", 423 | "$(inherited)", 424 | ); 425 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 426 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 427 | GCC_WARN_UNDECLARED_SELECTOR = YES; 428 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 429 | GCC_WARN_UNUSED_FUNCTION = YES; 430 | GCC_WARN_UNUSED_VARIABLE = YES; 431 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 432 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 433 | MTL_FAST_MATH = YES; 434 | ONLY_ACTIVE_ARCH = YES; 435 | SDKROOT = iphoneos; 436 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 437 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 438 | }; 439 | name = Debug; 440 | }; 441 | AB4578002740973200E7F889 /* Release */ = { 442 | isa = XCBuildConfiguration; 443 | buildSettings = { 444 | ALWAYS_SEARCH_USER_PATHS = NO; 445 | CLANG_ANALYZER_NONNULL = YES; 446 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 447 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 448 | CLANG_CXX_LIBRARY = "libc++"; 449 | CLANG_ENABLE_MODULES = YES; 450 | CLANG_ENABLE_OBJC_ARC = YES; 451 | CLANG_ENABLE_OBJC_WEAK = YES; 452 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 453 | CLANG_WARN_BOOL_CONVERSION = YES; 454 | CLANG_WARN_COMMA = YES; 455 | CLANG_WARN_CONSTANT_CONVERSION = YES; 456 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 457 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 458 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 459 | CLANG_WARN_EMPTY_BODY = YES; 460 | CLANG_WARN_ENUM_CONVERSION = YES; 461 | CLANG_WARN_INFINITE_RECURSION = YES; 462 | CLANG_WARN_INT_CONVERSION = YES; 463 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 464 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 465 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 466 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 467 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 468 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 469 | CLANG_WARN_STRICT_PROTOTYPES = YES; 470 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 471 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 472 | CLANG_WARN_UNREACHABLE_CODE = YES; 473 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 474 | COPY_PHASE_STRIP = NO; 475 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 476 | ENABLE_NS_ASSERTIONS = NO; 477 | ENABLE_STRICT_OBJC_MSGSEND = YES; 478 | GCC_C_LANGUAGE_STANDARD = gnu11; 479 | GCC_NO_COMMON_BLOCKS = YES; 480 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 481 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 482 | GCC_WARN_UNDECLARED_SELECTOR = YES; 483 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 484 | GCC_WARN_UNUSED_FUNCTION = YES; 485 | GCC_WARN_UNUSED_VARIABLE = YES; 486 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 487 | MTL_ENABLE_DEBUG_INFO = NO; 488 | MTL_FAST_MATH = YES; 489 | SDKROOT = iphoneos; 490 | SWIFT_COMPILATION_MODE = wholemodule; 491 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 492 | VALIDATE_PRODUCT = YES; 493 | }; 494 | name = Release; 495 | }; 496 | AB4578022740973200E7F889 /* Debug */ = { 497 | isa = XCBuildConfiguration; 498 | buildSettings = { 499 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 500 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 501 | CODE_SIGN_STYLE = Automatic; 502 | CURRENT_PROJECT_VERSION = 1; 503 | DEVELOPMENT_ASSET_PATHS = "\"mindmap-swiftui/Preview Content\""; 504 | DEVELOPMENT_TEAM = BA663759J3; 505 | ENABLE_PREVIEWS = YES; 506 | GENERATE_INFOPLIST_FILE = YES; 507 | INFOPLIST_FILE = "mindmap-swiftui/Info.plist"; 508 | INFOPLIST_KEY_CFBundleDisplayName = Mindmap; 509 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 510 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 511 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 512 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 513 | LD_RUNPATH_SEARCH_PATHS = ( 514 | "$(inherited)", 515 | "@executable_path/Frameworks", 516 | ); 517 | MARKETING_VERSION = 1.0; 518 | PRODUCT_BUNDLE_IDENTIFIER = "com.mindmap-swiftui"; 519 | PRODUCT_NAME = "$(TARGET_NAME)"; 520 | SWIFT_EMIT_LOC_STRINGS = YES; 521 | SWIFT_VERSION = 5.0; 522 | TARGETED_DEVICE_FAMILY = 2; 523 | }; 524 | name = Debug; 525 | }; 526 | AB4578032740973200E7F889 /* Release */ = { 527 | isa = XCBuildConfiguration; 528 | buildSettings = { 529 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 530 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 531 | CODE_SIGN_STYLE = Automatic; 532 | CURRENT_PROJECT_VERSION = 1; 533 | DEVELOPMENT_ASSET_PATHS = "\"mindmap-swiftui/Preview Content\""; 534 | DEVELOPMENT_TEAM = BA663759J3; 535 | ENABLE_PREVIEWS = YES; 536 | GENERATE_INFOPLIST_FILE = YES; 537 | INFOPLIST_FILE = "mindmap-swiftui/Info.plist"; 538 | INFOPLIST_KEY_CFBundleDisplayName = Mindmap; 539 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 540 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 541 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 542 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 543 | LD_RUNPATH_SEARCH_PATHS = ( 544 | "$(inherited)", 545 | "@executable_path/Frameworks", 546 | ); 547 | MARKETING_VERSION = 1.0; 548 | PRODUCT_BUNDLE_IDENTIFIER = "com.mindmap-swiftui"; 549 | PRODUCT_NAME = "$(TARGET_NAME)"; 550 | SWIFT_EMIT_LOC_STRINGS = YES; 551 | SWIFT_VERSION = 5.0; 552 | TARGETED_DEVICE_FAMILY = 2; 553 | }; 554 | name = Release; 555 | }; 556 | AB4578052740973200E7F889 /* Debug */ = { 557 | isa = XCBuildConfiguration; 558 | buildSettings = { 559 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 560 | BUNDLE_LOADER = "$(TEST_HOST)"; 561 | CODE_SIGN_STYLE = Automatic; 562 | CURRENT_PROJECT_VERSION = 1; 563 | DEVELOPMENT_TEAM = BA663759J3; 564 | GENERATE_INFOPLIST_FILE = YES; 565 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 566 | LD_RUNPATH_SEARCH_PATHS = ( 567 | "$(inherited)", 568 | "@executable_path/Frameworks", 569 | "@loader_path/Frameworks", 570 | ); 571 | MARKETING_VERSION = 1.0; 572 | PRODUCT_BUNDLE_IDENTIFIER = "com.mindmap-swiftuiTests"; 573 | PRODUCT_NAME = "$(TARGET_NAME)"; 574 | SWIFT_EMIT_LOC_STRINGS = NO; 575 | SWIFT_VERSION = 5.0; 576 | TARGETED_DEVICE_FAMILY = "1,2"; 577 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/mindmap-swiftui.app/mindmap-swiftui"; 578 | }; 579 | name = Debug; 580 | }; 581 | AB4578062740973200E7F889 /* Release */ = { 582 | isa = XCBuildConfiguration; 583 | buildSettings = { 584 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 585 | BUNDLE_LOADER = "$(TEST_HOST)"; 586 | CODE_SIGN_STYLE = Automatic; 587 | CURRENT_PROJECT_VERSION = 1; 588 | DEVELOPMENT_TEAM = BA663759J3; 589 | GENERATE_INFOPLIST_FILE = YES; 590 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 591 | LD_RUNPATH_SEARCH_PATHS = ( 592 | "$(inherited)", 593 | "@executable_path/Frameworks", 594 | "@loader_path/Frameworks", 595 | ); 596 | MARKETING_VERSION = 1.0; 597 | PRODUCT_BUNDLE_IDENTIFIER = "com.mindmap-swiftuiTests"; 598 | PRODUCT_NAME = "$(TARGET_NAME)"; 599 | SWIFT_EMIT_LOC_STRINGS = NO; 600 | SWIFT_VERSION = 5.0; 601 | TARGETED_DEVICE_FAMILY = "1,2"; 602 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/mindmap-swiftui.app/mindmap-swiftui"; 603 | }; 604 | name = Release; 605 | }; 606 | AB4578082740973200E7F889 /* Debug */ = { 607 | isa = XCBuildConfiguration; 608 | buildSettings = { 609 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 610 | CODE_SIGN_STYLE = Automatic; 611 | CURRENT_PROJECT_VERSION = 1; 612 | DEVELOPMENT_TEAM = BA663759J3; 613 | GENERATE_INFOPLIST_FILE = YES; 614 | LD_RUNPATH_SEARCH_PATHS = ( 615 | "$(inherited)", 616 | "@executable_path/Frameworks", 617 | "@loader_path/Frameworks", 618 | ); 619 | MARKETING_VERSION = 1.0; 620 | PRODUCT_BUNDLE_IDENTIFIER = "com.mindmap-swiftuiUITests"; 621 | PRODUCT_NAME = "$(TARGET_NAME)"; 622 | SWIFT_EMIT_LOC_STRINGS = NO; 623 | SWIFT_VERSION = 5.0; 624 | TARGETED_DEVICE_FAMILY = "1,2"; 625 | TEST_TARGET_NAME = "mindmap-swiftui"; 626 | }; 627 | name = Debug; 628 | }; 629 | AB4578092740973200E7F889 /* Release */ = { 630 | isa = XCBuildConfiguration; 631 | buildSettings = { 632 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 633 | CODE_SIGN_STYLE = Automatic; 634 | CURRENT_PROJECT_VERSION = 1; 635 | DEVELOPMENT_TEAM = BA663759J3; 636 | GENERATE_INFOPLIST_FILE = YES; 637 | LD_RUNPATH_SEARCH_PATHS = ( 638 | "$(inherited)", 639 | "@executable_path/Frameworks", 640 | "@loader_path/Frameworks", 641 | ); 642 | MARKETING_VERSION = 1.0; 643 | PRODUCT_BUNDLE_IDENTIFIER = "com.mindmap-swiftuiUITests"; 644 | PRODUCT_NAME = "$(TARGET_NAME)"; 645 | SWIFT_EMIT_LOC_STRINGS = NO; 646 | SWIFT_VERSION = 5.0; 647 | TARGETED_DEVICE_FAMILY = "1,2"; 648 | TEST_TARGET_NAME = "mindmap-swiftui"; 649 | }; 650 | name = Release; 651 | }; 652 | /* End XCBuildConfiguration section */ 653 | 654 | /* Begin XCConfigurationList section */ 655 | AB4577D82740972E00E7F889 /* Build configuration list for PBXProject "mindmap-swiftui" */ = { 656 | isa = XCConfigurationList; 657 | buildConfigurations = ( 658 | AB4577FF2740973200E7F889 /* Debug */, 659 | AB4578002740973200E7F889 /* Release */, 660 | ); 661 | defaultConfigurationIsVisible = 0; 662 | defaultConfigurationName = Release; 663 | }; 664 | AB4578012740973200E7F889 /* Build configuration list for PBXNativeTarget "mindmap-swiftui" */ = { 665 | isa = XCConfigurationList; 666 | buildConfigurations = ( 667 | AB4578022740973200E7F889 /* Debug */, 668 | AB4578032740973200E7F889 /* Release */, 669 | ); 670 | defaultConfigurationIsVisible = 0; 671 | defaultConfigurationName = Release; 672 | }; 673 | AB4578042740973200E7F889 /* Build configuration list for PBXNativeTarget "mindmap-swiftuiTests" */ = { 674 | isa = XCConfigurationList; 675 | buildConfigurations = ( 676 | AB4578052740973200E7F889 /* Debug */, 677 | AB4578062740973200E7F889 /* Release */, 678 | ); 679 | defaultConfigurationIsVisible = 0; 680 | defaultConfigurationName = Release; 681 | }; 682 | AB4578072740973200E7F889 /* Build configuration list for PBXNativeTarget "mindmap-swiftuiUITests" */ = { 683 | isa = XCConfigurationList; 684 | buildConfigurations = ( 685 | AB4578082740973200E7F889 /* Debug */, 686 | AB4578092740973200E7F889 /* Release */, 687 | ); 688 | defaultConfigurationIsVisible = 0; 689 | defaultConfigurationName = Release; 690 | }; 691 | /* End XCConfigurationList section */ 692 | }; 693 | rootObject = AB4577D52740972E00E7F889 /* Project object */; 694 | } 695 | -------------------------------------------------------------------------------- /mindmap-swiftui.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /mindmap-swiftui.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /mindmap-swiftui/Assets.xcassets/AppIcon.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andriybashta/mindmap-swiftui/b1f6174aee65750aa0fd664a4e95d2f9fa483db5/mindmap-swiftui/Assets.xcassets/AppIcon.appiconset/100.png -------------------------------------------------------------------------------- /mindmap-swiftui/Assets.xcassets/AppIcon.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andriybashta/mindmap-swiftui/b1f6174aee65750aa0fd664a4e95d2f9fa483db5/mindmap-swiftui/Assets.xcassets/AppIcon.appiconset/144.png -------------------------------------------------------------------------------- /mindmap-swiftui/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andriybashta/mindmap-swiftui/b1f6174aee65750aa0fd664a4e95d2f9fa483db5/mindmap-swiftui/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /mindmap-swiftui/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andriybashta/mindmap-swiftui/b1f6174aee65750aa0fd664a4e95d2f9fa483db5/mindmap-swiftui/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /mindmap-swiftui/Assets.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andriybashta/mindmap-swiftui/b1f6174aee65750aa0fd664a4e95d2f9fa483db5/mindmap-swiftui/Assets.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /mindmap-swiftui/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andriybashta/mindmap-swiftui/b1f6174aee65750aa0fd664a4e95d2f9fa483db5/mindmap-swiftui/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /mindmap-swiftui/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andriybashta/mindmap-swiftui/b1f6174aee65750aa0fd664a4e95d2f9fa483db5/mindmap-swiftui/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /mindmap-swiftui/Assets.xcassets/AppIcon.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andriybashta/mindmap-swiftui/b1f6174aee65750aa0fd664a4e95d2f9fa483db5/mindmap-swiftui/Assets.xcassets/AppIcon.appiconset/50.png -------------------------------------------------------------------------------- /mindmap-swiftui/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andriybashta/mindmap-swiftui/b1f6174aee65750aa0fd664a4e95d2f9fa483db5/mindmap-swiftui/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /mindmap-swiftui/Assets.xcassets/AppIcon.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andriybashta/mindmap-swiftui/b1f6174aee65750aa0fd664a4e95d2f9fa483db5/mindmap-swiftui/Assets.xcassets/AppIcon.appiconset/72.png -------------------------------------------------------------------------------- /mindmap-swiftui/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andriybashta/mindmap-swiftui/b1f6174aee65750aa0fd664a4e95d2f9fa483db5/mindmap-swiftui/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /mindmap-swiftui/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andriybashta/mindmap-swiftui/b1f6174aee65750aa0fd664a4e95d2f9fa483db5/mindmap-swiftui/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /mindmap-swiftui/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "20.png", 5 | "idiom" : "ipad", 6 | "scale" : "1x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "40.png", 11 | "idiom" : "ipad", 12 | "scale" : "2x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "29.png", 17 | "idiom" : "ipad", 18 | "scale" : "1x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "58.png", 23 | "idiom" : "ipad", 24 | "scale" : "2x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "40.png", 29 | "idiom" : "ipad", 30 | "scale" : "1x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "filename" : "80.png", 35 | "idiom" : "ipad", 36 | "scale" : "2x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "50.png", 41 | "idiom" : "ipad", 42 | "scale" : "1x", 43 | "size" : "50x50" 44 | }, 45 | { 46 | "filename" : "100.png", 47 | "idiom" : "ipad", 48 | "scale" : "2x", 49 | "size" : "50x50" 50 | }, 51 | { 52 | "filename" : "72.png", 53 | "idiom" : "ipad", 54 | "scale" : "1x", 55 | "size" : "72x72" 56 | }, 57 | { 58 | "filename" : "144.png", 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "72x72" 62 | }, 63 | { 64 | "filename" : "76.png", 65 | "idiom" : "ipad", 66 | "scale" : "1x", 67 | "size" : "76x76" 68 | }, 69 | { 70 | "filename" : "152.png", 71 | "idiom" : "ipad", 72 | "scale" : "2x", 73 | "size" : "76x76" 74 | }, 75 | { 76 | "filename" : "167.png", 77 | "idiom" : "ipad", 78 | "scale" : "2x", 79 | "size" : "83.5x83.5" 80 | }, 81 | { 82 | "filename" : "appstore.png", 83 | "idiom" : "ios-marketing", 84 | "scale" : "1x", 85 | "size" : "1024x1024" 86 | } 87 | ], 88 | "info" : { 89 | "author" : "xcode", 90 | "version" : 1 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /mindmap-swiftui/Assets.xcassets/AppIcon.appiconset/appstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andriybashta/mindmap-swiftui/b1f6174aee65750aa0fd664a4e95d2f9fa483db5/mindmap-swiftui/Assets.xcassets/AppIcon.appiconset/appstore.png -------------------------------------------------------------------------------- /mindmap-swiftui/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /mindmap-swiftui/FilesListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FilesListView.swift 3 | // mindmap-swiftui 4 | // 5 | // Created by Bashta on 14.11.2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | class FileList: ObservableObject { 11 | @Published var files: [File] = [ 12 | File.init(mesh: Mesh.sampleMesh(), selection: SelectionHandler.init()) 13 | ] 14 | 15 | func addNote() { 16 | files.append(File.init(mesh: Mesh.sampleMesh(), selection: SelectionHandler.init())) 17 | } 18 | } 19 | 20 | class File: Identifiable { 21 | @Published var mesh: Mesh 22 | @Published var selection: SelectionHandler 23 | 24 | init(mesh: Mesh = .sampleMesh(), 25 | selection: SelectionHandler = .init()) { 26 | self.mesh = mesh 27 | self.selection = selection 28 | } 29 | 30 | var id: String { 31 | mesh.rootNodeID.uuidString 32 | } 33 | } 34 | 35 | struct FilesListView: View { 36 | @StateObject var list: FileList = .init() 37 | 38 | var body: some View { 39 | NavigationView { 40 | VStack { 41 | Trailing 42 | List(list.files) { item in 43 | NavigationLink(item.mesh.rootNode().text) { 44 | SurfaceView(mesh: item.mesh, selection: item.selection) 45 | .navigationTitle(Text(item.mesh.rootNode().text)) 46 | } 47 | } 48 | } 49 | .padding(.vertical, 50) 50 | } 51 | .navigationTitle("Mindmap") 52 | .navigationViewStyle(.stack) 53 | .navigationBarItems(trailing: Trailing) 54 | } 55 | 56 | var Trailing: some View { 57 | Button("Add note") { 58 | list.addNote() 59 | } 60 | } 61 | } 62 | 63 | struct FilesListView_Previews: PreviewProvider { 64 | static var previews: some View { 65 | FilesListView() 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /mindmap-swiftui/Help/CGPoint+Help.swift: -------------------------------------------------------------------------------- 1 | 2 | import CoreGraphics 3 | 4 | extension CGPoint { 5 | func translatedBy(x: CGFloat, y: CGFloat) -> CGPoint { 6 | return CGPoint(x: self.x + x, y: self.y + y) 7 | } 8 | } 9 | 10 | extension CGPoint { 11 | func alignCenterInParent(_ parent: CGSize) -> CGPoint { 12 | let x = parent.width/2 + self.x 13 | let y = parent.height/2 + self.y 14 | return CGPoint(x: x, y: y) 15 | } 16 | 17 | func scaledFrom(_ factor: CGFloat) -> CGPoint { 18 | return CGPoint( 19 | x: self.x * factor, 20 | y: self.y * factor) 21 | } 22 | } 23 | 24 | extension CGSize { 25 | func scaledDownTo(_ factor: CGFloat) -> CGSize { 26 | return CGSize(width: width/factor, height: height/factor) 27 | } 28 | 29 | var length: CGFloat { 30 | return sqrt(pow(width, 2) + pow(height, 2)) 31 | } 32 | 33 | var inverted: CGSize { 34 | return CGSize(width: -width, height: -height) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /mindmap-swiftui/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /mindmap-swiftui/Model/Edge.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import CoreGraphics 4 | 5 | typealias EdgeID = UUID 6 | 7 | struct Edge: Identifiable { 8 | var id = EdgeID() 9 | var start: NodeID 10 | var end: NodeID 11 | } 12 | 13 | struct EdgeProxy: Identifiable { 14 | var id: EdgeID 15 | var start: CGPoint 16 | var end: CGPoint 17 | } 18 | 19 | extension Edge { 20 | static func == (lhs: Edge, rhs: Edge) -> Bool { 21 | return lhs.start == rhs.start && lhs.end == rhs.end 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /mindmap-swiftui/Model/Mesh+Demo.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import CoreGraphics 4 | 5 | extension Mesh { 6 | static func sampleMesh() -> Mesh { 7 | let mesh = Mesh() 8 | mesh.updateNodeText(mesh.rootNode(), string: "New note") 9 | [(0, "mind 1"), 10 | (120, "mind 2"), 11 | (240, "mind 3")].forEach { (angle, name) in 12 | let point = mesh.pointWithCenter(center: .zero, radius: 200, angle: angle.radians) 13 | let node = mesh.addChild(mesh.rootNode(), at: point) 14 | mesh.updateNodeText(node, string: name) 15 | } 16 | return mesh 17 | } 18 | 19 | func addChildrenRecursive(to node: Node, distance: CGFloat, generation: Int) { 20 | let labels = ["A", "B", "C", "D", "E", "F"] 21 | guard generation < labels.count else { 22 | return 23 | } 24 | 25 | let childCount = Int.random(in: 1..<4) 26 | var count = 0 27 | while count < childCount { 28 | count += 1 29 | let position = positionForNewChild(node, length: distance) 30 | let child = addChild(node, at: position) 31 | updateNodeText(child, string: "\(labels[generation])\(count + 1)") 32 | addChildrenRecursive(to: child, distance: distance + 200.0, generation: generation + 1) 33 | } 34 | } 35 | } 36 | 37 | extension Int { 38 | var radians: CGFloat { 39 | CGFloat(self) * CGFloat.pi/180.0 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /mindmap-swiftui/Model/Mesh+MathHelp.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import CoreGraphics 4 | 5 | extension Mesh { 6 | public func positionForNewChild(_ parent: Node, length: CGFloat) -> CGPoint { 7 | let childEdges = edges.filter { $0.start == parent.id } 8 | if let grandparentedge = edges.filter({ $0.end == parent.id }).first, let grandparent = nodeWithID(grandparentedge.start) { 9 | let baseAngle = angleFrom(start: grandparent.position, end: parent.position) 10 | let childBasedAngle = positionForChildAtIndex(childEdges.count, baseAngle: baseAngle) 11 | let newpoint = pointWithCenter(center: parent.position, radius: length, angle: childBasedAngle) 12 | return newpoint 13 | } 14 | return CGPoint(x: 200, y: 200) 15 | } 16 | 17 | /// get angle for n'th child in order delta * 0,1,-1,2,-2 18 | func positionForChildAtIndex(_ index: Int, baseAngle: CGFloat) -> CGFloat { 19 | let jitter = CGFloat.random(in: CGFloat(-1.0)...CGFloat(1.0)) * CGFloat.pi/32.0 20 | guard index > 0 else { return baseAngle + jitter } 21 | 22 | let level = (index + 1)/2 23 | let polarity: CGFloat = index % 2 == 0 ? -1.0:1.0 24 | 25 | let delta = CGFloat.pi/6.0 + jitter 26 | return baseAngle + polarity * delta * CGFloat(level) 27 | } 28 | 29 | /// angle in radians 30 | func pointWithCenter(center: CGPoint, radius: CGFloat, angle: CGFloat) -> CGPoint { 31 | let deltax = radius*cos(angle) 32 | let deltay = radius*sin(angle) 33 | let newpoint = CGPoint(x: center.x + deltax, y: center.y + deltay) 34 | return newpoint 35 | } 36 | 37 | func angleFrom(start: CGPoint, end: CGPoint) -> CGFloat { 38 | var deltax = end.x - start.x 39 | let deltay = end.y - start.y 40 | if abs(deltax) < 0.001 { 41 | deltax = 0.001 42 | } 43 | let angle = atan(deltay/abs(deltax)) 44 | return deltax > 0 ? angle: CGFloat.pi - angle 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /mindmap-swiftui/Model/Mesh.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import CoreGraphics 4 | 5 | class Mesh: ObservableObject, Identifiable { 6 | var id: String { 7 | rootNodeID.uuidString 8 | } 9 | 10 | let rootNodeID: NodeID 11 | @Published var nodes: [Node] = [] 12 | @Published var editingText: String 13 | 14 | init() { 15 | self.editingText = "" 16 | let root = Node(text: "root") 17 | rootNodeID = root.id 18 | addNode(root) 19 | } 20 | 21 | var edges: [Edge] = [] { 22 | didSet { 23 | rebuildLinks() 24 | } 25 | } 26 | @Published var links: [EdgeProxy] = [] 27 | 28 | 29 | 30 | func rebuildLinks() { 31 | links = edges.compactMap { edge in 32 | let snode = nodes.filter({ $0.id == edge.start }).first 33 | let enode = nodes.filter({ $0.id == edge.end }).first 34 | if let snode = snode, let enode = enode { 35 | return EdgeProxy(id: edge.id, start: snode.position, end: enode.position) 36 | } 37 | return nil 38 | } 39 | } 40 | 41 | func rootNode() -> Node { 42 | guard let root = nodes.filter({ $0.id == rootNodeID }).first else { 43 | fatalError("mesh is invalid - no root") 44 | } 45 | return root 46 | } 47 | 48 | func nodeWithID(_ nodeID: NodeID) -> Node? { 49 | return nodes.filter({ $0.id == nodeID }).first 50 | } 51 | 52 | func replace(_ node: Node, with replacement: Node) { 53 | var newSet = nodes.filter { $0.id != node.id } 54 | newSet.append(replacement) 55 | nodes = newSet 56 | } 57 | } 58 | 59 | extension Mesh { 60 | func updateNodeText(_ srcNode: Node, string: String) { 61 | var newNode = srcNode 62 | newNode.text = string 63 | replace(srcNode, with: newNode) 64 | } 65 | 66 | func positionNode(_ node: Node, position: CGPoint) { 67 | var movedNode = node 68 | movedNode.position = position 69 | replace(node, with: movedNode) 70 | rebuildLinks() 71 | } 72 | 73 | func processNodeTranslation(_ translation: CGSize, nodes: [DragInfo]) { 74 | nodes.forEach { draginfo in 75 | if let node = nodeWithID(draginfo.id) { 76 | let nextPosition = draginfo.originalPosition.translatedBy(x: translation.width, y: translation.height) 77 | self.positionNode(node, position: nextPosition) 78 | } 79 | } 80 | } 81 | } 82 | 83 | extension Mesh { 84 | func addNode(_ node: Node) { 85 | nodes.append(node) 86 | } 87 | 88 | func connect(_ parent: Node, to child: Node) { 89 | let newedge = Edge(start: parent.id, end: child.id) 90 | let exists = edges.contains(where: { edge in 91 | return newedge == edge 92 | }) 93 | 94 | guard exists == false else { 95 | return 96 | } 97 | 98 | edges.append(newedge) 99 | } 100 | } 101 | 102 | extension Mesh { 103 | @discardableResult func addChild(_ parent: Node, at point: CGPoint? = nil) -> Node { 104 | let target = point ?? parent.position 105 | let child = Node(position: target, text: "child") 106 | addNode(child) 107 | connect(parent, to: child) 108 | rebuildLinks() 109 | return child 110 | } 111 | 112 | @discardableResult func addSibling(_ node: Node) -> Node? { 113 | guard node.id != rootNodeID else { 114 | return nil 115 | } 116 | 117 | let parentedges = edges.filter({ $0.end == node.id }) 118 | if 119 | let parentedge = parentedges.first, 120 | let parentnode = nodeWithID(parentedge.start) { 121 | let sibling = addChild(parentnode) 122 | return sibling 123 | } 124 | return nil 125 | } 126 | 127 | func deleteNodes(_ nodesToDelete: [NodeID]) { 128 | for id in nodesToDelete where id != rootNodeID { 129 | if let delete = nodes.firstIndex(where: { $0.id == id }) { 130 | nodes.remove(at: delete) 131 | let remainingEdges = edges.filter({ $0.end != id && $0.start != id }) 132 | edges = remainingEdges 133 | } 134 | } 135 | rebuildLinks() 136 | } 137 | 138 | func deleteNodes(_ nodesToDelete: [Node]) { 139 | deleteNodes(nodesToDelete.map({ $0.id })) 140 | } 141 | } 142 | 143 | extension Mesh { 144 | func locateParent(_ node: Node) -> Node? { 145 | let parentedges = edges.filter { $0.end == node.id } 146 | if let parentedge = parentedges.first, 147 | let parentnode = nodeWithID(parentedge.start) { 148 | return parentnode 149 | } 150 | return nil 151 | } 152 | 153 | func distanceFromRoot(_ node: Node, distance: Int = 0) -> Int? { 154 | if node.id == rootNodeID { return distance } 155 | 156 | if let ancestor = locateParent(node) { 157 | if ancestor.id == rootNodeID { 158 | return distance + 1 159 | } else { 160 | return distanceFromRoot(ancestor, distance: distance + 1) 161 | } 162 | } 163 | return nil 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /mindmap-swiftui/Model/Node.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import CoreGraphics 4 | 5 | typealias NodeID = UUID 6 | 7 | struct Node: Identifiable { 8 | var id: NodeID = NodeID() 9 | var position: CGPoint = .zero 10 | var text: String = "" 11 | 12 | var visualID: String { 13 | return id.uuidString 14 | + "\(text.hashValue)" 15 | } 16 | } 17 | 18 | extension Node { 19 | static func == (lhs: Node, rhs: Node) -> Bool { 20 | return lhs.id == rhs.id 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /mindmap-swiftui/Model/SelectionHandler.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import CoreGraphics 4 | 5 | struct DragInfo { 6 | var id: NodeID 7 | var originalPosition: CGPoint 8 | } 9 | 10 | class SelectionHandler: ObservableObject { 11 | @Published var draggingNodes: [DragInfo] = [] 12 | @Published private(set) var selectedNodeIDs: [NodeID] = [] 13 | 14 | @Published var editingText: String = "" 15 | 16 | func selectNode(_ node: Node) { 17 | selectedNodeIDs = [node.id] 18 | editingText = node.text 19 | } 20 | 21 | func isNodeSelected(_ node: Node) -> Bool { 22 | return selectedNodeIDs.contains(node.id) 23 | } 24 | 25 | func selectedNodes(in mesh: Mesh) -> [Node] { 26 | return selectedNodeIDs.compactMap { mesh.nodeWithID($0) } 27 | } 28 | 29 | func onlySelectedNode(in mesh: Mesh) -> Node? { 30 | let selectedNodes = self.selectedNodes(in: mesh) 31 | if selectedNodes.count == 1 { 32 | return selectedNodes.first 33 | } 34 | return nil 35 | } 36 | 37 | func startDragging(_ mesh: Mesh) { 38 | draggingNodes = selectedNodes(in: mesh) 39 | .map { DragInfo(id: $0.id, originalPosition: $0.position) } 40 | } 41 | 42 | func stopDragging(_ mesh: Mesh) { 43 | draggingNodes = [] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /mindmap-swiftui/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /mindmap-swiftui/View Stack/BoringListView.swift: -------------------------------------------------------------------------------- 1 | 2 | import SwiftUI 3 | 4 | struct BoringListView: View { 5 | @ObservedObject var mesh: Mesh 6 | @ObservedObject var selection: SelectionHandler 7 | 8 | func indent(_ node: Node) -> CGFloat { 9 | let base = 20.0 10 | 11 | return CGFloat(mesh.distanceFromRoot(node) ?? 0) * CGFloat(base) 12 | } 13 | 14 | var body: some View { 15 | List(mesh.nodes, id: \.id) { node in 16 | Text(node.text) 17 | .padding(EdgeInsets( 18 | top: 0, 19 | leading: self.indent(node), 20 | bottom: 0, 21 | trailing: 0)) 22 | } 23 | } 24 | } 25 | 26 | struct BoringListView_Previews: PreviewProvider { 27 | static var previews: some View { 28 | let mesh = Mesh.sampleMesh() 29 | let selection = SelectionHandler() 30 | 31 | return BoringListView(mesh: mesh, selection: selection) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /mindmap-swiftui/View Stack/EdgeMapView.swift: -------------------------------------------------------------------------------- 1 | 2 | import SwiftUI 3 | 4 | struct EdgeMapView: View { 5 | @Binding var edges: [EdgeProxy] 6 | 7 | var body: some View { 8 | ZStack { 9 | ForEach(edges) { edge in 10 | EdgeView(edge: edge) 11 | .stroke(Color.black, lineWidth: 3.0) 12 | } 13 | } 14 | } 15 | } 16 | 17 | struct EdgeMapView_Previews: PreviewProvider { 18 | static let proxy1 = EdgeProxy( 19 | id: EdgeID(), 20 | start: .zero, 21 | end: CGPoint(x: -100, y: 30)) 22 | static let proxy2 = EdgeProxy( 23 | id: EdgeID(), 24 | start: .zero, 25 | end: CGPoint(x: 100, y: 30)) 26 | 27 | @State static var edges = [proxy1, proxy2] 28 | 29 | static var previews: some View { 30 | EdgeMapView(edges: $edges) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /mindmap-swiftui/View Stack/EdgeView.swift: -------------------------------------------------------------------------------- 1 | 2 | 3 | import SwiftUI 4 | 5 | typealias AnimatablePoint = AnimatablePair 6 | typealias AnimatableCorners = AnimatablePair 7 | 8 | struct EdgeView: Shape { 9 | var startx: CGFloat = 0 10 | var starty: CGFloat = 0 11 | var endx: CGFloat = 0 12 | var endy: CGFloat = 0 13 | 14 | // 1 15 | init(edge: EdgeProxy) { 16 | // 2 17 | startx = edge.start.x 18 | starty = edge.start.y 19 | endx = edge.end.x 20 | endy = edge.end.y 21 | } 22 | 23 | // 3 24 | func path(in rect: CGRect) -> Path { 25 | var linkPath = Path() 26 | linkPath.move(to: CGPoint(x: startx, y: starty) 27 | .alignCenterInParent(rect.size)) 28 | linkPath.addLine(to: CGPoint(x: endx, y:endy) 29 | .alignCenterInParent(rect.size)) 30 | return linkPath 31 | } 32 | 33 | var animatableData: AnimatableCorners { 34 | get { AnimatablePair( 35 | AnimatablePair(startx, starty), 36 | AnimatablePair(endx, endy)) 37 | } 38 | set { 39 | startx = newValue.first.first 40 | starty = newValue.first.second 41 | endx = newValue.second.first 42 | endy = newValue.second.second 43 | } 44 | } 45 | } 46 | 47 | struct EdgeView_Previews: PreviewProvider { 48 | static var previews: some View { 49 | let edge1 = EdgeProxy( 50 | id: UUID(), 51 | start: CGPoint(x: -100, y: -100), 52 | end: CGPoint(x: 100, y: 100)) 53 | let edge2 = EdgeProxy( 54 | id: UUID(), 55 | start: CGPoint(x: 100, y: -100), 56 | end: CGPoint(x: -100, y: 100)) 57 | return ZStack { 58 | EdgeView(edge: edge1).stroke(lineWidth: 4) 59 | EdgeView(edge: edge2).stroke(Color.green, lineWidth: 2) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /mindmap-swiftui/View Stack/MapView.swift: -------------------------------------------------------------------------------- 1 | 2 | import SwiftUI 3 | 4 | struct MapView: View { 5 | @ObservedObject var selection: SelectionHandler 6 | @ObservedObject var mesh: Mesh 7 | 8 | var body: some View { 9 | ZStack { 10 | Rectangle().fill(Color.green) 11 | EdgeMapView(edges: $mesh.links) 12 | NodeMapView(selection: selection, nodes: $mesh.nodes) 13 | } 14 | } 15 | } 16 | 17 | struct MapView_Previews: PreviewProvider { 18 | static var previews: some View { 19 | let mesh = Mesh() 20 | let child1 = Node(position: CGPoint(x: 100, y: 200), text: "child 1") 21 | let child2 = Node(position: CGPoint(x: -100, y: 200), text: "child 2") 22 | [child1, child2].forEach { 23 | mesh.addNode($0) 24 | mesh.connect(mesh.rootNode(), to: $0) 25 | } 26 | mesh.connect(child1, to: child2) 27 | let selection = SelectionHandler() 28 | return MapView(selection: selection, mesh: mesh) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mindmap-swiftui/View Stack/NodeMapView.swift: -------------------------------------------------------------------------------- 1 | 2 | import SwiftUI 3 | 4 | struct NodeMapView: View { 5 | @ObservedObject var selection: SelectionHandler 6 | @Binding var nodes: [Node] 7 | 8 | var body: some View { 9 | ZStack { 10 | ForEach(nodes, id: \.visualID) { node in 11 | NodeView(node: node, selection: self.selection) 12 | .offset(x: node.position.x, y: node.position.y) 13 | .onTapGesture { 14 | self.selection.selectNode(node) 15 | } 16 | } 17 | } 18 | } 19 | } 20 | 21 | struct NodeMapView_Previews: PreviewProvider { 22 | static let node1 = Node(position: CGPoint(x: -100, y: -30), text: "hello") 23 | static let node2 = Node(position: CGPoint(x: 100, y: 30), text: "world") 24 | @State static var nodes = [node1, node2] 25 | 26 | static var previews: some View { 27 | let selection = SelectionHandler() 28 | return NodeMapView(selection: selection, nodes: $nodes) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mindmap-swiftui/View Stack/NodeView.swift: -------------------------------------------------------------------------------- 1 | 2 | import SwiftUI 3 | 4 | struct NodeView: View { 5 | static let width = CGFloat(100) 6 | // 1 7 | @State var node: Node 8 | //2 9 | @ObservedObject var selection: SelectionHandler 10 | //3 11 | var isSelected: Bool { 12 | return selection.isNodeSelected(node) 13 | } 14 | 15 | var body: some View { 16 | RoundedRectangle(cornerRadius: 20) 17 | .fill(Color.yellow) 18 | .overlay(RoundedRectangle(cornerRadius: 20) 19 | .stroke(isSelected ? Color.red : Color.black, lineWidth: isSelected ? 5 : 3)) 20 | .overlay(Text(node.text) 21 | .multilineTextAlignment(.center) 22 | .padding(EdgeInsets(top: 0, leading: 8, bottom: 0, trailing: 8))) 23 | .frame(width: NodeView.width, height: NodeView.width, alignment: .center) 24 | } 25 | } 26 | 27 | struct NodeView_Previews: PreviewProvider { 28 | static var previews: some View { 29 | let selection1 = SelectionHandler() 30 | let node1 = Node(text: "hello world") 31 | let selection2 = SelectionHandler() 32 | let node2 = Node(text: "I'm selected, look at me") 33 | selection2.selectNode(node2) 34 | 35 | return VStack { 36 | NodeView(node: node1, selection: selection1) 37 | NodeView(node: node2, selection: selection2) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /mindmap-swiftui/View Stack/SurfaceView.swift: -------------------------------------------------------------------------------- 1 | 2 | import SwiftUI 3 | 4 | struct SurfaceView: View { 5 | @ObservedObject var mesh: Mesh 6 | @ObservedObject var selection: SelectionHandler 7 | 8 | //dragging 9 | @State var portalPosition: CGPoint = .zero 10 | @State var dragOffset: CGSize = .zero 11 | @State var isDragging: Bool = false 12 | @State var isDraggingMesh: Bool = false 13 | 14 | //zooming 15 | @State var zoomScale: CGFloat = 1.0 16 | @State var initialZoomScale: CGFloat? 17 | @State var initialPortalPosition: CGPoint? 18 | 19 | var body: some View { 20 | VStack { 21 | TextField("Type the text of new mind…", text: $selection.editingText, onCommit: { 22 | if let node = self.selection.onlySelectedNode(in: self.mesh) { 23 | self.mesh.updateNodeText(node, string: self.self.selection.editingText) 24 | } 25 | }) 26 | 27 | .textFieldStyle(.roundedBorder) 28 | Button("Add node") { 29 | mesh.addNode(Node.init(id: NodeID.init(uuidString: UUID().uuidString)!, position: CGPoint.init(x: 20, y: 40), text: "Bla")) 30 | } 31 | // 2 32 | GeometryReader { geometry in 33 | // 3 34 | ZStack { 35 | Rectangle().fill(Color.red) 36 | MapView(selection: self.selection, mesh: self.mesh) 37 | .scaleEffect(self.zoomScale) 38 | // 4 39 | .offset( 40 | x: self.portalPosition.x + self.dragOffset.width, 41 | y: self.portalPosition.y + self.dragOffset.height) 42 | .animation(.easeIn) 43 | } 44 | .gesture(DragGesture() 45 | .onChanged { value in 46 | self.processDragChange(value, containerSize: geometry.size) 47 | } 48 | .onEnded { value in 49 | self.processDragEnd(value) 50 | }) 51 | .gesture(MagnificationGesture() 52 | .onChanged { value in 53 | // 1 54 | if self.initialZoomScale == nil { 55 | self.initialZoomScale = self.zoomScale 56 | self.initialPortalPosition = self.portalPosition 57 | } 58 | self.processScaleChange(value) 59 | } 60 | .onEnded { value in 61 | // 2 62 | self.processScaleChange(value) 63 | self.initialZoomScale = nil 64 | self.initialPortalPosition = nil 65 | }) 66 | } 67 | } 68 | } 69 | } 70 | 71 | struct SurfaceView_Previews: PreviewProvider { 72 | static var previews: some View { 73 | let mesh = Mesh.sampleMesh() 74 | let selection = SelectionHandler() 75 | return SurfaceView(mesh: mesh, selection: selection) 76 | } 77 | } 78 | 79 | private extension SurfaceView { 80 | // 1 81 | func distance(from pointA: CGPoint, to pointB: CGPoint) -> CGFloat { 82 | let xdelta = pow(pointA.x - pointB.x, 2) 83 | let ydelta = pow(pointA.y - pointB.y, 2) 84 | 85 | return sqrt(xdelta + ydelta) 86 | } 87 | 88 | // 2 89 | func hitTest(point: CGPoint, parent: CGSize) -> Node? { 90 | for node in mesh.nodes { 91 | let endPoint = node.position 92 | .scaledFrom(zoomScale) 93 | .alignCenterInParent(parent) 94 | .translatedBy(x: portalPosition.x, y: portalPosition.y) 95 | let dist = distance(from: point, to: endPoint) / zoomScale 96 | 97 | //3 98 | if dist < NodeView.width / 2.0 { 99 | return node 100 | } 101 | } 102 | return nil 103 | } 104 | 105 | // 4 106 | func processNodeTranslation(_ translation: CGSize) { 107 | guard !selection.draggingNodes.isEmpty else { return } 108 | let scaledTranslation = translation.scaledDownTo(zoomScale) 109 | mesh.processNodeTranslation( 110 | scaledTranslation, 111 | nodes: selection.draggingNodes) 112 | } 113 | 114 | func processDragChange(_ value: DragGesture.Value, containerSize: CGSize) { 115 | // 1 116 | if !isDragging { 117 | isDragging = true 118 | 119 | if let node = hitTest( 120 | point: value.startLocation, 121 | parent: containerSize) { 122 | isDraggingMesh = false 123 | selection.selectNode(node) 124 | // 2 125 | selection.startDragging(mesh) 126 | } else { 127 | isDraggingMesh = true 128 | } 129 | } 130 | 131 | // 3 132 | if isDraggingMesh { 133 | dragOffset = value.translation 134 | } else { 135 | processNodeTranslation(value.translation) 136 | } 137 | } 138 | 139 | // 4 140 | func processDragEnd(_ value: DragGesture.Value) { 141 | isDragging = false 142 | dragOffset = .zero 143 | 144 | if isDraggingMesh { 145 | portalPosition = CGPoint( 146 | x: portalPosition.x + value.translation.width, 147 | y: portalPosition.y + value.translation.height) 148 | } else { 149 | processNodeTranslation(value.translation) 150 | selection.stopDragging(mesh) 151 | } 152 | } 153 | 154 | // 1 155 | func scaledOffset(_ scale: CGFloat, initialValue: CGPoint) -> CGPoint { 156 | let newx = initialValue.x*scale 157 | let newy = initialValue.y*scale 158 | return CGPoint(x: newx, y: newy) 159 | } 160 | 161 | func clampedScale(_ scale: CGFloat, initialValue: CGFloat?) -> (scale: CGFloat, didClamp: Bool) { 162 | let minScale: CGFloat = 0.1 163 | let maxScale: CGFloat = 2.0 164 | let raw = scale.magnitude * (initialValue ?? maxScale) 165 | let value = max(minScale, min(maxScale, raw)) 166 | let didClamp = raw != value 167 | return (value, didClamp) 168 | } 169 | 170 | func processScaleChange(_ value: CGFloat) { 171 | let clamped = clampedScale(value, initialValue: initialZoomScale) 172 | zoomScale = clamped.scale 173 | if !clamped.didClamp, 174 | let point = initialPortalPosition { 175 | portalPosition = scaledOffset(value, initialValue: point) 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /mindmap-swiftui/mindmap_swiftuiApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // mindmap_swiftuiApp.swift 3 | // mindmap-swiftui 4 | // 5 | // Created by Bashta on 14.11.2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct mindmap_swiftuiApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | FilesListView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /mindmap-swiftuiTests/mindmap_swiftuiTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // mindmap_swiftuiTests.swift 3 | // mindmap-swiftuiTests 4 | // 5 | // Created by Bashta on 14.11.2021. 6 | // 7 | 8 | import XCTest 9 | @testable import mindmap_swiftui 10 | 11 | class mindmap_swiftuiTests: XCTestCase { 12 | 13 | override func setUpWithError() throws { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | } 16 | 17 | override func tearDownWithError() throws { 18 | // Put teardown code here. This method is called after the invocation of each test method in the class. 19 | } 20 | 21 | func testExample() throws { 22 | // This is an example of a functional test case. 23 | // Use XCTAssert and related functions to verify your tests produce the correct results. 24 | } 25 | 26 | func testPerformanceExample() throws { 27 | // This is an example of a performance test case. 28 | self.measure { 29 | // Put the code you want to measure the time of here. 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /mindmap-swiftuiUITests/mindmap_swiftuiUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // mindmap_swiftuiUITests.swift 3 | // mindmap-swiftuiUITests 4 | // 5 | // Created by Bashta on 14.11.2021. 6 | // 7 | 8 | import XCTest 9 | 10 | class mindmap_swiftuiUITests: XCTestCase { 11 | 12 | override func setUpWithError() throws { 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | 15 | // In UI tests it is usually best to stop immediately when a failure occurs. 16 | continueAfterFailure = false 17 | 18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 19 | } 20 | 21 | override func tearDownWithError() throws { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | } 24 | 25 | func testExample() throws { 26 | // UI tests must launch the application that they test. 27 | let app = XCUIApplication() 28 | app.launch() 29 | 30 | // Use recording to get started writing UI tests. 31 | // Use XCTAssert and related functions to verify your tests produce the correct results. 32 | } 33 | 34 | func testLaunchPerformance() throws { 35 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { 36 | // This measures how long it takes to launch your application. 37 | measure(metrics: [XCTApplicationLaunchMetric()]) { 38 | XCUIApplication().launch() 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /mindmap-swiftuiUITests/mindmap_swiftuiUITestsLaunchTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // mindmap_swiftuiUITestsLaunchTests.swift 3 | // mindmap-swiftuiUITests 4 | // 5 | // Created by Bashta on 14.11.2021. 6 | // 7 | 8 | import XCTest 9 | 10 | class mindmap_swiftuiUITestsLaunchTests: XCTestCase { 11 | 12 | override class var runsForEachTargetApplicationUIConfiguration: Bool { 13 | true 14 | } 15 | 16 | override func setUpWithError() throws { 17 | continueAfterFailure = false 18 | } 19 | 20 | func testLaunch() throws { 21 | let app = XCUIApplication() 22 | app.launch() 23 | 24 | // Insert steps here to perform after app launch but before taking a screenshot, 25 | // such as logging into a test account or navigating somewhere in the app 26 | 27 | let attachment = XCTAttachment(screenshot: app.screenshot()) 28 | attachment.name = "Launch Screen" 29 | attachment.lifetime = .keepAlways 30 | add(attachment) 31 | } 32 | } 33 | --------------------------------------------------------------------------------