├── .gitignore ├── LICENSE ├── README.md ├── RazeMind.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── warrenburton.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcshareddata │ └── IDETemplateMacros.plist ├── RazeMind.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── RazeMind ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon-167.png │ │ ├── icon-20.png │ │ ├── icon-20@2x-1.png │ │ ├── icon-20@2x.png │ │ ├── icon-20@3x.png │ │ ├── icon-29.png │ │ ├── icon-29@2x-1.png │ │ ├── icon-29@2x.png │ │ ├── icon-29@3x.png │ │ ├── icon-40.png │ │ ├── icon-40@2x-1.png │ │ ├── icon-40@2x.png │ │ ├── icon-40@3x.png │ │ ├── icon-60@2x.png │ │ ├── icon-60@3x.png │ │ ├── icon-76.png │ │ ├── icon-76@2x.png │ │ └── launch-screen.png │ ├── Contents.json │ ├── colors │ │ ├── Contents.json │ │ ├── disabled.colorset │ │ │ └── Contents.json │ │ ├── rw-dark.colorset │ │ │ └── Contents.json │ │ ├── rw-green.colorset │ │ │ └── Contents.json │ │ └── rw-light.colorset │ │ │ └── Contents.json │ └── launch-screen.imageset │ │ ├── Contents.json │ │ └── launch-screen.png ├── Base.lproj │ └── LaunchScreen.storyboard ├── Help │ └── UIApplication+Directories.swift ├── Infrastructure │ ├── SimpleLogger.swift │ └── StorageHandler.swift ├── RazeMind.entitlements ├── RazeMind.swift └── Views │ └── BoringListView.swift └── RazeMindCore ├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Package.swift ├── README.md ├── Sources └── RazeMindCore │ ├── Model │ ├── CGPoint+Help.swift │ ├── Edge.swift │ ├── Mesh+Demo.swift │ ├── Mesh+MathHelp.swift │ ├── Mesh+Storage.swift │ ├── Mesh.swift │ ├── Node.swift │ ├── RazeMindCore.swift │ └── SelectionHandler.swift │ └── View Stack │ ├── EdgeMapView.swift │ ├── EdgeView.swift │ ├── MapView.swift │ ├── NodeMapView.swift │ ├── NodeView.swift │ └── SurfaceView.swift └── Tests └── RazeMindCoreTests └── RazeMindCoreTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | */xcuserdata -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2020] [Warren Burton] 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RazeMind 2 | 3 | ## Introduction 4 | 5 | This is a demo infinite 2D surface renderer for SwiftUI. 6 | 7 | Extends the project started in [this tutorial](https://www.raywenderlich.com/7705231-creating-a-mind-map-ui-in-swiftui) 8 | 9 | -------------------------------------------------------------------------------- /RazeMind.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 53; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | AD6C18C62434A9880030CC51 /* StorageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6C18C52434A9880030CC51 /* StorageHandler.swift */; }; 11 | AD6C18C82434AA420030CC51 /* UIApplication+Directories.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6C18C72434AA420030CC51 /* UIApplication+Directories.swift */; }; 12 | AD6C18CA2434ABCA0030CC51 /* SimpleLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6C18C92434ABCA0030CC51 /* SimpleLogger.swift */; }; 13 | ADA4F1492A23C404005D4374 /* RazeMindCore in Frameworks */ = {isa = PBXBuildFile; productRef = ADA4F1482A23C404005D4374 /* RazeMindCore */; }; 14 | ADA4F14E2A23CBC5005D4374 /* BoringListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADA4F14D2A23CBC5005D4374 /* BoringListView.swift */; }; 15 | ADA4F1872A23D1B4005D4374 /* RazeMind.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADA4F1862A23D1B4005D4374 /* RazeMind.swift */; }; 16 | ADC9091823C3D31500394ADF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = ADC9091723C3D31500394ADF /* Assets.xcassets */; }; 17 | ADC9091E23C3D31600394ADF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = ADC9091C23C3D31600394ADF /* LaunchScreen.storyboard */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | AD6C18C52434A9880030CC51 /* StorageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageHandler.swift; sourceTree = ""; }; 22 | AD6C18C72434AA420030CC51 /* UIApplication+Directories.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIApplication+Directories.swift"; sourceTree = ""; }; 23 | AD6C18C92434ABCA0030CC51 /* SimpleLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleLogger.swift; sourceTree = ""; }; 24 | ADA4F14D2A23CBC5005D4374 /* BoringListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoringListView.swift; sourceTree = ""; }; 25 | ADA4F1862A23D1B4005D4374 /* RazeMind.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RazeMind.swift; sourceTree = ""; }; 26 | ADC9090B23C3D31300394ADF /* RazeMind.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RazeMind.app; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | ADC9091723C3D31500394ADF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 28 | ADC9091D23C3D31600394ADF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 29 | ADC9093323C3DAC900394ADF /* RazeMind.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RazeMind.entitlements; sourceTree = ""; }; 30 | ADCEE6702A23C1D60043C907 /* RazeMindCore */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = RazeMindCore; sourceTree = ""; }; 31 | /* End PBXFileReference section */ 32 | 33 | /* Begin PBXFrameworksBuildPhase section */ 34 | ADC9090823C3D31300394ADF /* Frameworks */ = { 35 | isa = PBXFrameworksBuildPhase; 36 | buildActionMask = 2147483647; 37 | files = ( 38 | ADA4F1492A23C404005D4374 /* RazeMindCore in Frameworks */, 39 | ); 40 | runOnlyForDeploymentPostprocessing = 0; 41 | }; 42 | /* End PBXFrameworksBuildPhase section */ 43 | 44 | /* Begin PBXGroup section */ 45 | AD7C16A523D644A500A3FEA3 /* Infrastructure */ = { 46 | isa = PBXGroup; 47 | children = ( 48 | AD6C18C92434ABCA0030CC51 /* SimpleLogger.swift */, 49 | AD6C18C52434A9880030CC51 /* StorageHandler.swift */, 50 | ); 51 | path = Infrastructure; 52 | sourceTree = ""; 53 | }; 54 | AD7C16A623D644C100A3FEA3 /* Help */ = { 55 | isa = PBXGroup; 56 | children = ( 57 | AD6C18C72434AA420030CC51 /* UIApplication+Directories.swift */, 58 | ); 59 | path = Help; 60 | sourceTree = ""; 61 | }; 62 | ADA4F1472A23C404005D4374 /* Frameworks */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | ); 66 | name = Frameworks; 67 | sourceTree = ""; 68 | }; 69 | ADA4F14A2A23C998005D4374 /* Views */ = { 70 | isa = PBXGroup; 71 | children = ( 72 | ADA4F14D2A23CBC5005D4374 /* BoringListView.swift */, 73 | ); 74 | path = Views; 75 | sourceTree = ""; 76 | }; 77 | ADC9090223C3D31300394ADF = { 78 | isa = PBXGroup; 79 | children = ( 80 | ADCEE66F2A23C1D60043C907 /* Packages */, 81 | ADC9090D23C3D31300394ADF /* RazeMind */, 82 | ADC9090C23C3D31300394ADF /* Products */, 83 | ADA4F1472A23C404005D4374 /* Frameworks */, 84 | ); 85 | indentWidth = 2; 86 | sourceTree = ""; 87 | tabWidth = 2; 88 | }; 89 | ADC9090C23C3D31300394ADF /* Products */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | ADC9090B23C3D31300394ADF /* RazeMind.app */, 93 | ); 94 | name = Products; 95 | sourceTree = ""; 96 | }; 97 | ADC9090D23C3D31300394ADF /* RazeMind */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | ADA4F14A2A23C998005D4374 /* Views */, 101 | ADC9093323C3DAC900394ADF /* RazeMind.entitlements */, 102 | ADC9091723C3D31500394ADF /* Assets.xcassets */, 103 | AD7C16A523D644A500A3FEA3 /* Infrastructure */, 104 | AD7C16A623D644C100A3FEA3 /* Help */, 105 | ADC9091C23C3D31600394ADF /* LaunchScreen.storyboard */, 106 | ADA4F1862A23D1B4005D4374 /* RazeMind.swift */, 107 | ); 108 | path = RazeMind; 109 | sourceTree = ""; 110 | }; 111 | ADCEE66F2A23C1D60043C907 /* Packages */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | ADCEE6702A23C1D60043C907 /* RazeMindCore */, 115 | ); 116 | name = Packages; 117 | sourceTree = ""; 118 | }; 119 | /* End PBXGroup section */ 120 | 121 | /* Begin PBXNativeTarget section */ 122 | ADC9090A23C3D31300394ADF /* RazeMind */ = { 123 | isa = PBXNativeTarget; 124 | buildConfigurationList = ADC9092D23C3D31600394ADF /* Build configuration list for PBXNativeTarget "RazeMind" */; 125 | buildPhases = ( 126 | ADC9090723C3D31300394ADF /* Sources */, 127 | ADC9090823C3D31300394ADF /* Frameworks */, 128 | ADC9090923C3D31300394ADF /* Resources */, 129 | ); 130 | buildRules = ( 131 | ); 132 | dependencies = ( 133 | ); 134 | name = RazeMind; 135 | packageProductDependencies = ( 136 | ADA4F1482A23C404005D4374 /* RazeMindCore */, 137 | ); 138 | productName = Spiral6; 139 | productReference = ADC9090B23C3D31300394ADF /* RazeMind.app */; 140 | productType = "com.apple.product-type.application"; 141 | }; 142 | /* End PBXNativeTarget section */ 143 | 144 | /* Begin PBXProject section */ 145 | ADC9090323C3D31300394ADF /* Project object */ = { 146 | isa = PBXProject; 147 | attributes = { 148 | BuildIndependentTargetsInParallel = YES; 149 | LastSwiftUpdateCheck = 1130; 150 | LastUpgradeCheck = 1430; 151 | ORGANIZATIONNAME = "Warren Burton"; 152 | TargetAttributes = { 153 | ADC9090A23C3D31300394ADF = { 154 | CreatedOnToolsVersion = 11.3; 155 | }; 156 | }; 157 | }; 158 | buildConfigurationList = ADC9090623C3D31300394ADF /* Build configuration list for PBXProject "RazeMind" */; 159 | compatibilityVersion = "Xcode 9.3"; 160 | developmentRegion = en; 161 | hasScannedForEncodings = 0; 162 | knownRegions = ( 163 | en, 164 | Base, 165 | ); 166 | mainGroup = ADC9090223C3D31300394ADF; 167 | productRefGroup = ADC9090C23C3D31300394ADF /* Products */; 168 | projectDirPath = ""; 169 | projectRoot = ""; 170 | targets = ( 171 | ADC9090A23C3D31300394ADF /* RazeMind */, 172 | ); 173 | }; 174 | /* End PBXProject section */ 175 | 176 | /* Begin PBXResourcesBuildPhase section */ 177 | ADC9090923C3D31300394ADF /* Resources */ = { 178 | isa = PBXResourcesBuildPhase; 179 | buildActionMask = 2147483647; 180 | files = ( 181 | ADC9091E23C3D31600394ADF /* LaunchScreen.storyboard in Resources */, 182 | ADC9091823C3D31500394ADF /* Assets.xcassets in Resources */, 183 | ); 184 | runOnlyForDeploymentPostprocessing = 0; 185 | }; 186 | /* End PBXResourcesBuildPhase section */ 187 | 188 | /* Begin PBXSourcesBuildPhase section */ 189 | ADC9090723C3D31300394ADF /* Sources */ = { 190 | isa = PBXSourcesBuildPhase; 191 | buildActionMask = 2147483647; 192 | files = ( 193 | ADA4F1872A23D1B4005D4374 /* RazeMind.swift in Sources */, 194 | ADA4F14E2A23CBC5005D4374 /* BoringListView.swift in Sources */, 195 | AD6C18CA2434ABCA0030CC51 /* SimpleLogger.swift in Sources */, 196 | AD6C18C82434AA420030CC51 /* UIApplication+Directories.swift in Sources */, 197 | AD6C18C62434A9880030CC51 /* StorageHandler.swift in Sources */, 198 | ); 199 | runOnlyForDeploymentPostprocessing = 0; 200 | }; 201 | /* End PBXSourcesBuildPhase section */ 202 | 203 | /* Begin PBXVariantGroup section */ 204 | ADC9091C23C3D31600394ADF /* LaunchScreen.storyboard */ = { 205 | isa = PBXVariantGroup; 206 | children = ( 207 | ADC9091D23C3D31600394ADF /* Base */, 208 | ); 209 | name = LaunchScreen.storyboard; 210 | sourceTree = ""; 211 | }; 212 | /* End PBXVariantGroup section */ 213 | 214 | /* Begin XCBuildConfiguration section */ 215 | ADC9092B23C3D31600394ADF /* Debug */ = { 216 | isa = XCBuildConfiguration; 217 | buildSettings = { 218 | ALWAYS_SEARCH_USER_PATHS = NO; 219 | CLANG_ANALYZER_NONNULL = YES; 220 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 221 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 222 | CLANG_CXX_LIBRARY = "libc++"; 223 | CLANG_ENABLE_MODULES = YES; 224 | CLANG_ENABLE_OBJC_ARC = YES; 225 | CLANG_ENABLE_OBJC_WEAK = YES; 226 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 227 | CLANG_WARN_BOOL_CONVERSION = YES; 228 | CLANG_WARN_COMMA = YES; 229 | CLANG_WARN_CONSTANT_CONVERSION = YES; 230 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 231 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 232 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 233 | CLANG_WARN_EMPTY_BODY = YES; 234 | CLANG_WARN_ENUM_CONVERSION = YES; 235 | CLANG_WARN_INFINITE_RECURSION = YES; 236 | CLANG_WARN_INT_CONVERSION = YES; 237 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 238 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 239 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 240 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 241 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 242 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 243 | CLANG_WARN_STRICT_PROTOTYPES = YES; 244 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 245 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 246 | CLANG_WARN_UNREACHABLE_CODE = YES; 247 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 248 | COPY_PHASE_STRIP = NO; 249 | DEBUG_INFORMATION_FORMAT = dwarf; 250 | ENABLE_STRICT_OBJC_MSGSEND = YES; 251 | ENABLE_TESTABILITY = YES; 252 | GCC_C_LANGUAGE_STANDARD = gnu11; 253 | GCC_DYNAMIC_NO_PIC = NO; 254 | GCC_NO_COMMON_BLOCKS = YES; 255 | GCC_OPTIMIZATION_LEVEL = 0; 256 | GCC_PREPROCESSOR_DEFINITIONS = ( 257 | "DEBUG=1", 258 | "$(inherited)", 259 | ); 260 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 261 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 262 | GCC_WARN_UNDECLARED_SELECTOR = YES; 263 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 264 | GCC_WARN_UNUSED_FUNCTION = YES; 265 | GCC_WARN_UNUSED_VARIABLE = YES; 266 | GENERATE_INFOPLIST_FILE = YES; 267 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 268 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 269 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 270 | MTL_FAST_MATH = YES; 271 | ONLY_ACTIVE_ARCH = YES; 272 | SDKROOT = iphoneos; 273 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 274 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 275 | }; 276 | name = Debug; 277 | }; 278 | ADC9092C23C3D31600394ADF /* Release */ = { 279 | isa = XCBuildConfiguration; 280 | buildSettings = { 281 | ALWAYS_SEARCH_USER_PATHS = NO; 282 | CLANG_ANALYZER_NONNULL = YES; 283 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 284 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 285 | CLANG_CXX_LIBRARY = "libc++"; 286 | CLANG_ENABLE_MODULES = YES; 287 | CLANG_ENABLE_OBJC_ARC = YES; 288 | CLANG_ENABLE_OBJC_WEAK = YES; 289 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 290 | CLANG_WARN_BOOL_CONVERSION = YES; 291 | CLANG_WARN_COMMA = YES; 292 | CLANG_WARN_CONSTANT_CONVERSION = YES; 293 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 294 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 295 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 296 | CLANG_WARN_EMPTY_BODY = YES; 297 | CLANG_WARN_ENUM_CONVERSION = YES; 298 | CLANG_WARN_INFINITE_RECURSION = YES; 299 | CLANG_WARN_INT_CONVERSION = YES; 300 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 301 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 302 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 303 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 304 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 305 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 306 | CLANG_WARN_STRICT_PROTOTYPES = YES; 307 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 308 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 309 | CLANG_WARN_UNREACHABLE_CODE = YES; 310 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 311 | COPY_PHASE_STRIP = NO; 312 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 313 | ENABLE_NS_ASSERTIONS = NO; 314 | ENABLE_STRICT_OBJC_MSGSEND = YES; 315 | GCC_C_LANGUAGE_STANDARD = gnu11; 316 | GCC_NO_COMMON_BLOCKS = YES; 317 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 318 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 319 | GCC_WARN_UNDECLARED_SELECTOR = YES; 320 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 321 | GCC_WARN_UNUSED_FUNCTION = YES; 322 | GCC_WARN_UNUSED_VARIABLE = YES; 323 | GENERATE_INFOPLIST_FILE = YES; 324 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 325 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 326 | MTL_ENABLE_DEBUG_INFO = NO; 327 | MTL_FAST_MATH = YES; 328 | SDKROOT = iphoneos; 329 | SWIFT_COMPILATION_MODE = wholemodule; 330 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 331 | VALIDATE_PRODUCT = YES; 332 | }; 333 | name = Release; 334 | }; 335 | ADC9092E23C3D31600394ADF /* Debug */ = { 336 | isa = XCBuildConfiguration; 337 | buildSettings = { 338 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 339 | CODE_SIGN_ENTITLEMENTS = RazeMind/RazeMind.entitlements; 340 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; 341 | CODE_SIGN_STYLE = Automatic; 342 | DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; 343 | DEVELOPMENT_TEAM = 77EQ427L36; 344 | ENABLE_PREVIEWS = YES; 345 | IPHONEOS_DEPLOYMENT_TARGET = 15.6; 346 | LD_RUNPATH_SEARCH_PATHS = ( 347 | "$(inherited)", 348 | "@executable_path/Frameworks", 349 | ); 350 | PRODUCT_BUNDLE_IDENTIFIER = com.razeware.RazeMind2020; 351 | PRODUCT_NAME = "$(TARGET_NAME)"; 352 | SUPPORTS_MACCATALYST = YES; 353 | SWIFT_VERSION = 5.0; 354 | TARGETED_DEVICE_FAMILY = "1,2"; 355 | }; 356 | name = Debug; 357 | }; 358 | ADC9092F23C3D31600394ADF /* Release */ = { 359 | isa = XCBuildConfiguration; 360 | buildSettings = { 361 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 362 | CODE_SIGN_ENTITLEMENTS = RazeMind/RazeMind.entitlements; 363 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; 364 | CODE_SIGN_STYLE = Automatic; 365 | DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; 366 | DEVELOPMENT_TEAM = 77EQ427L36; 367 | ENABLE_PREVIEWS = YES; 368 | IPHONEOS_DEPLOYMENT_TARGET = 15.6; 369 | LD_RUNPATH_SEARCH_PATHS = ( 370 | "$(inherited)", 371 | "@executable_path/Frameworks", 372 | ); 373 | PRODUCT_BUNDLE_IDENTIFIER = com.razeware.RazeMind2020; 374 | PRODUCT_NAME = "$(TARGET_NAME)"; 375 | SUPPORTS_MACCATALYST = YES; 376 | SWIFT_VERSION = 5.0; 377 | TARGETED_DEVICE_FAMILY = "1,2"; 378 | }; 379 | name = Release; 380 | }; 381 | /* End XCBuildConfiguration section */ 382 | 383 | /* Begin XCConfigurationList section */ 384 | ADC9090623C3D31300394ADF /* Build configuration list for PBXProject "RazeMind" */ = { 385 | isa = XCConfigurationList; 386 | buildConfigurations = ( 387 | ADC9092B23C3D31600394ADF /* Debug */, 388 | ADC9092C23C3D31600394ADF /* Release */, 389 | ); 390 | defaultConfigurationIsVisible = 0; 391 | defaultConfigurationName = Release; 392 | }; 393 | ADC9092D23C3D31600394ADF /* Build configuration list for PBXNativeTarget "RazeMind" */ = { 394 | isa = XCConfigurationList; 395 | buildConfigurations = ( 396 | ADC9092E23C3D31600394ADF /* Debug */, 397 | ADC9092F23C3D31600394ADF /* Release */, 398 | ); 399 | defaultConfigurationIsVisible = 0; 400 | defaultConfigurationName = Release; 401 | }; 402 | /* End XCConfigurationList section */ 403 | 404 | /* Begin XCSwiftPackageProductDependency section */ 405 | ADA4F1482A23C404005D4374 /* RazeMindCore */ = { 406 | isa = XCSwiftPackageProductDependency; 407 | productName = RazeMindCore; 408 | }; 409 | /* End XCSwiftPackageProductDependency section */ 410 | }; 411 | rootObject = ADC9090323C3D31300394ADF /* Project object */; 412 | } 413 | -------------------------------------------------------------------------------- /RazeMind.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /RazeMind.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /RazeMind.xcodeproj/project.xcworkspace/xcuserdata/warrenburton.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warrenburton/RazeMind/45f9046eb6e5ba557a362dd8ce51f107634d5cae/RazeMind.xcodeproj/project.xcworkspace/xcuserdata/warrenburton.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /RazeMind.xcodeproj/xcshareddata/IDETemplateMacros.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FILEHEADER 6 | MIT License 7 | // 8 | //Copyright (c) [2020] [Warren Burton] 9 | // 10 | //Permission is hereby granted, free of charge, to any person obtaining a copy 11 | //of this software and associated documentation files (the "Software"), to deal 12 | //in the Software without restriction, including without limitation the rights 13 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | //copies of the Software, and to permit persons to whom the Software is 15 | //furnished to do so, subject to the following conditions: 16 | // 17 | //The above copyright notice and this permission notice shall be included in all 18 | //copies or substantial portions of the Software. 19 | // 20 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | //SOFTWARE. 27 | 28 | 29 | -------------------------------------------------------------------------------- /RazeMind.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /RazeMind.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /RazeMind/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "icon-20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "icon-20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "icon-29@2x-1.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "icon-29@3x.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "icon-40@2x.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "icon-40@3x.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "icon-60@2x.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "icon-60@3x.png", 49 | "scale" : "3x" 50 | }, 51 | { 52 | "size" : "20x20", 53 | "idiom" : "ipad", 54 | "filename" : "icon-20.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "icon-20@2x-1.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "29x29", 65 | "idiom" : "ipad", 66 | "filename" : "icon-29.png", 67 | "scale" : "1x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "icon-29@2x.png", 73 | "scale" : "2x" 74 | }, 75 | { 76 | "size" : "40x40", 77 | "idiom" : "ipad", 78 | "filename" : "icon-40.png", 79 | "scale" : "1x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "icon-40@2x-1.png", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "size" : "76x76", 89 | "idiom" : "ipad", 90 | "filename" : "icon-76.png", 91 | "scale" : "1x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "icon-76@2x.png", 97 | "scale" : "2x" 98 | }, 99 | { 100 | "size" : "83.5x83.5", 101 | "idiom" : "ipad", 102 | "filename" : "icon-167.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "1024x1024", 107 | "idiom" : "ios-marketing", 108 | "filename" : "launch-screen.png", 109 | "scale" : "1x" 110 | } 111 | ], 112 | "info" : { 113 | "version" : 1, 114 | "author" : "xcode" 115 | } 116 | } -------------------------------------------------------------------------------- /RazeMind/Assets.xcassets/AppIcon.appiconset/icon-167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warrenburton/RazeMind/45f9046eb6e5ba557a362dd8ce51f107634d5cae/RazeMind/Assets.xcassets/AppIcon.appiconset/icon-167.png -------------------------------------------------------------------------------- /RazeMind/Assets.xcassets/AppIcon.appiconset/icon-20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warrenburton/RazeMind/45f9046eb6e5ba557a362dd8ce51f107634d5cae/RazeMind/Assets.xcassets/AppIcon.appiconset/icon-20.png -------------------------------------------------------------------------------- /RazeMind/Assets.xcassets/AppIcon.appiconset/icon-20@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warrenburton/RazeMind/45f9046eb6e5ba557a362dd8ce51f107634d5cae/RazeMind/Assets.xcassets/AppIcon.appiconset/icon-20@2x-1.png -------------------------------------------------------------------------------- /RazeMind/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warrenburton/RazeMind/45f9046eb6e5ba557a362dd8ce51f107634d5cae/RazeMind/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png -------------------------------------------------------------------------------- /RazeMind/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warrenburton/RazeMind/45f9046eb6e5ba557a362dd8ce51f107634d5cae/RazeMind/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png -------------------------------------------------------------------------------- /RazeMind/Assets.xcassets/AppIcon.appiconset/icon-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warrenburton/RazeMind/45f9046eb6e5ba557a362dd8ce51f107634d5cae/RazeMind/Assets.xcassets/AppIcon.appiconset/icon-29.png -------------------------------------------------------------------------------- /RazeMind/Assets.xcassets/AppIcon.appiconset/icon-29@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warrenburton/RazeMind/45f9046eb6e5ba557a362dd8ce51f107634d5cae/RazeMind/Assets.xcassets/AppIcon.appiconset/icon-29@2x-1.png -------------------------------------------------------------------------------- /RazeMind/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warrenburton/RazeMind/45f9046eb6e5ba557a362dd8ce51f107634d5cae/RazeMind/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png -------------------------------------------------------------------------------- /RazeMind/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warrenburton/RazeMind/45f9046eb6e5ba557a362dd8ce51f107634d5cae/RazeMind/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png -------------------------------------------------------------------------------- /RazeMind/Assets.xcassets/AppIcon.appiconset/icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warrenburton/RazeMind/45f9046eb6e5ba557a362dd8ce51f107634d5cae/RazeMind/Assets.xcassets/AppIcon.appiconset/icon-40.png -------------------------------------------------------------------------------- /RazeMind/Assets.xcassets/AppIcon.appiconset/icon-40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warrenburton/RazeMind/45f9046eb6e5ba557a362dd8ce51f107634d5cae/RazeMind/Assets.xcassets/AppIcon.appiconset/icon-40@2x-1.png -------------------------------------------------------------------------------- /RazeMind/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warrenburton/RazeMind/45f9046eb6e5ba557a362dd8ce51f107634d5cae/RazeMind/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png -------------------------------------------------------------------------------- /RazeMind/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warrenburton/RazeMind/45f9046eb6e5ba557a362dd8ce51f107634d5cae/RazeMind/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png -------------------------------------------------------------------------------- /RazeMind/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warrenburton/RazeMind/45f9046eb6e5ba557a362dd8ce51f107634d5cae/RazeMind/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png -------------------------------------------------------------------------------- /RazeMind/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warrenburton/RazeMind/45f9046eb6e5ba557a362dd8ce51f107634d5cae/RazeMind/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png -------------------------------------------------------------------------------- /RazeMind/Assets.xcassets/AppIcon.appiconset/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warrenburton/RazeMind/45f9046eb6e5ba557a362dd8ce51f107634d5cae/RazeMind/Assets.xcassets/AppIcon.appiconset/icon-76.png -------------------------------------------------------------------------------- /RazeMind/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warrenburton/RazeMind/45f9046eb6e5ba557a362dd8ce51f107634d5cae/RazeMind/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png -------------------------------------------------------------------------------- /RazeMind/Assets.xcassets/AppIcon.appiconset/launch-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warrenburton/RazeMind/45f9046eb6e5ba557a362dd8ce51f107634d5cae/RazeMind/Assets.xcassets/AppIcon.appiconset/launch-screen.png -------------------------------------------------------------------------------- /RazeMind/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /RazeMind/Assets.xcassets/colors/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /RazeMind/Assets.xcassets/colors/disabled.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0.744", 13 | "alpha" : "1.000", 14 | "blue" : "0.744", 15 | "green" : "0.744" 16 | } 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /RazeMind/Assets.xcassets/colors/rw-dark.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0.200", 13 | "alpha" : "1.000", 14 | "blue" : "0.200", 15 | "green" : "0.200" 16 | } 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /RazeMind/Assets.xcassets/colors/rw-green.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0.000", 13 | "alpha" : "1.000", 14 | "blue" : "0.216", 15 | "green" : "0.408" 16 | } 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /RazeMind/Assets.xcassets/colors/rw-light.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0.949", 13 | "alpha" : "1.000", 14 | "blue" : "0.980", 15 | "green" : "0.965" 16 | } 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /RazeMind/Assets.xcassets/launch-screen.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "launch-screen.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /RazeMind/Assets.xcassets/launch-screen.imageset/launch-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warrenburton/RazeMind/45f9046eb6e5ba557a362dd8ce51f107634d5cae/RazeMind/Assets.xcassets/launch-screen.imageset/launch-screen.png -------------------------------------------------------------------------------- /RazeMind/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /RazeMind/Help/UIApplication+Directories.swift: -------------------------------------------------------------------------------- 1 | //MIT License 2 | // 3 | //Copyright (c) [2020] [Warren Burton] 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 | 23 | import UIKit 24 | 25 | extension UIApplication { 26 | static func cacheDirectory() -> URL { 27 | guard let cachepath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).last else { 28 | fatalError("unable to get system cache directory - serious problems") 29 | } 30 | return URL(fileURLWithPath: cachepath) 31 | } 32 | 33 | static func documentsDirectory() -> URL { 34 | guard let docspath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).last else { 35 | fatalError("unable to get system docs directory - serious problems") 36 | } 37 | return URL(fileURLWithPath: docspath) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /RazeMind/Infrastructure/SimpleLogger.swift: -------------------------------------------------------------------------------- 1 | //MIT License 2 | // 3 | //Copyright (c) [2020] [Warren Burton] 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 | 23 | import Foundation 24 | 25 | func DLog(_ items: Any..., separator: String = " ", terminator: String = "\n") { 26 | #if DEBUG 27 | for item in items { 28 | print(item, terminator: " ") 29 | } 30 | print("") 31 | #endif 32 | } 33 | -------------------------------------------------------------------------------- /RazeMind/Infrastructure/StorageHandler.swift: -------------------------------------------------------------------------------- 1 | //MIT License 2 | // 3 | //Copyright (c) [2020] [Warren Burton] 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 | 23 | import UIKit 24 | import RazeMindCore 25 | 26 | /// Encapsulates file ops for the mesh data 27 | class StorageHandler { 28 | 29 | let documentURL = UIApplication.documentsDirectory().appendingPathComponent("default.rwmesh") 30 | 31 | /// save mesh data to a single file 32 | func save(_ mesh: MeshStorageProxy) throws { 33 | let data = try JSONEncoder().encode(mesh) 34 | try data.write(to: documentURL) 35 | } 36 | 37 | func restore() -> MeshStorageProxy { 38 | do { 39 | let data = try Data(contentsOf: documentURL) 40 | let proxy = try JSONDecoder().decode(MeshStorageProxy.self , from: data) 41 | return proxy 42 | } catch { 43 | DLog("*** oh no! restore error (pass to error system in full impl) -", error) 44 | } 45 | return Mesh().storageObject 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /RazeMind/RazeMind.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /RazeMind/RazeMind.swift: -------------------------------------------------------------------------------- 1 | //MIT License 2 | // 3 | //Copyright (c) [2020] [Warren Burton] 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 | 23 | import SwiftUI 24 | import RazeMindCore 25 | 26 | @main 27 | struct RazeMind: App { 28 | 29 | var mesh = restore() 30 | var selection = SelectionHandler() 31 | 32 | var body: some Scene { 33 | WindowGroup { 34 | SurfaceView(mesh: mesh, selection: selection) 35 | } 36 | } 37 | 38 | static func restore() -> Mesh { 39 | let proxy = StorageHandler().restore() 40 | let mesh = Mesh(storage: proxy) 41 | return mesh 42 | } 43 | 44 | } 45 | 46 | -------------------------------------------------------------------------------- /RazeMind/Views/BoringListView.swift: -------------------------------------------------------------------------------- 1 | /// Copyright (c) 2020 Razeware LLC 2 | /// 3 | /// Permission is hereby granted, free of charge, to any person obtaining a copy 4 | /// of this software and associated documentation files (the "Software"), to deal 5 | /// in the Software without restriction, including without limitation the rights 6 | /// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | /// copies of the Software, and to permit persons to whom the Software is 8 | /// furnished to do so, subject to the following conditions: 9 | /// 10 | /// The above copyright notice and this permission notice shall be included in 11 | /// all copies or substantial portions of the Software. 12 | /// 13 | /// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish, 14 | /// distribute, sublicense, create a derivative work, and/or sell copies of the 15 | /// Software in any work that is designed, intended, or marketed for pedagogical or 16 | /// instructional purposes related to programming, coding, application development, 17 | /// or information technology. Permission for such use, copying, modification, 18 | /// merger, publication, distribution, sublicensing, creation of derivative works, 19 | /// or sale is expressly withheld. 20 | /// 21 | /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | /// THE SOFTWARE. 28 | 29 | import SwiftUI 30 | import RazeMindCore 31 | 32 | struct BoringListView: View { 33 | 34 | @ObservedObject var mesh: Mesh 35 | @ObservedObject var selection: SelectionHandler 36 | 37 | func indent(_ node: Node) -> CGFloat { 38 | let base = 20.0 39 | return CGFloat(mesh.distanceFromRoot(node) ?? 0) * CGFloat(base) 40 | } 41 | 42 | var body: some View { 43 | List(mesh.nodes, id: \.id) { node in 44 | Text(node.text) 45 | .padding(EdgeInsets(top: 0, 46 | leading: self.indent(node), 47 | bottom: 0, 48 | trailing: 0)) 49 | } 50 | } 51 | } 52 | 53 | struct BoringListView_Previews: PreviewProvider { 54 | static var previews: some View { 55 | 56 | let mesh = Mesh.sampleMesh() 57 | let selection = SelectionHandler() 58 | 59 | return BoringListView(mesh: mesh, selection: selection) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /RazeMindCore/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/config/registries.json 8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 9 | .netrc 10 | -------------------------------------------------------------------------------- /RazeMindCore/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /RazeMindCore/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.8 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "RazeMindCore", platforms: [.iOS("15.6")], 8 | products: [ 9 | .library( 10 | name: "RazeMindCore", 11 | targets: ["RazeMindCore"]), 12 | ], 13 | dependencies: [ 14 | ], 15 | targets: [ 16 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 17 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 18 | .target( 19 | name: "RazeMindCore", 20 | dependencies: []), 21 | .testTarget( 22 | name: "RazeMindCoreTests", 23 | dependencies: ["RazeMindCore"]), 24 | ] 25 | ) 26 | -------------------------------------------------------------------------------- /RazeMindCore/README.md: -------------------------------------------------------------------------------- 1 | # RazeMindCore 2 | 3 | A description of this package. 4 | -------------------------------------------------------------------------------- /RazeMindCore/Sources/RazeMindCore/Model/CGPoint+Help.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGPoint+Help.swift 3 | // Spiral5 4 | // 5 | // Created by Warren Burton on 29/12/2019. 6 | // Copyright © 2019 Warren Burton. All rights reserved. 7 | // 8 | 9 | import CoreGraphics 10 | 11 | public extension CGPoint { 12 | func translatedBy(x: CGFloat, y: CGFloat) -> CGPoint { 13 | return CGPoint(x: self.x + x, 14 | y: self.y + y) 15 | } 16 | } 17 | 18 | public extension CGPoint { 19 | 20 | func alignCenterInParent(_ parent: CGSize) -> CGPoint { 21 | let x = parent.width/2 + self.x 22 | let y = parent.height/2 + self.y 23 | return CGPoint(x: x, y: y) 24 | } 25 | 26 | func scaledFrom(_ factor: CGFloat) -> CGPoint { 27 | return CGPoint(x: self.x * factor, 28 | y: self.y * factor) 29 | } 30 | } 31 | 32 | public extension CGSize { 33 | func scaledDownTo(_ factor: CGFloat) -> CGSize { 34 | return CGSize(width: width/factor, height: height/factor) 35 | } 36 | 37 | var length: CGFloat { 38 | return sqrt(pow(width, 2) + pow(height, 2)) 39 | } 40 | 41 | var inverted: CGSize { 42 | return CGSize(width: -width, height: -height) 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /RazeMindCore/Sources/RazeMindCore/Model/Edge.swift: -------------------------------------------------------------------------------- 1 | /// Copyright (c) 2020 Razeware LLC 2 | /// 3 | /// Permission is hereby granted, free of charge, to any person obtaining a copy 4 | /// of this software and associated documentation files (the "Software"), to deal 5 | /// in the Software without restriction, including without limitation the rights 6 | /// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | /// copies of the Software, and to permit persons to whom the Software is 8 | /// furnished to do so, subject to the following conditions: 9 | /// 10 | /// The above copyright notice and this permission notice shall be included in 11 | /// all copies or substantial portions of the Software. 12 | /// 13 | /// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish, 14 | /// distribute, sublicense, create a derivative work, and/or sell copies of the 15 | /// Software in any work that is designed, intended, or marketed for pedagogical or 16 | /// instructional purposes related to programming, coding, application development, 17 | /// or information technology. Permission for such use, copying, modification, 18 | /// merger, publication, distribution, sublicensing, creation of derivative works, 19 | /// or sale is expressly withheld. 20 | /// 21 | /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | /// THE SOFTWARE. 28 | 29 | import Foundation 30 | import CoreGraphics 31 | 32 | public typealias EdgeID = UUID 33 | 34 | public struct Edge: Identifiable { 35 | public var id = EdgeID() 36 | public var start: NodeID 37 | public var end: NodeID 38 | } 39 | 40 | public struct EdgeProxy: Identifiable { 41 | public var id: EdgeID 42 | public var start: CGPoint 43 | public var end: CGPoint 44 | 45 | public init(id: EdgeID, start: CGPoint, end: CGPoint) { 46 | self.id = id 47 | self.start = start 48 | self.end = end 49 | } 50 | } 51 | 52 | public extension Edge { 53 | static func == (lhs: Edge, rhs: Edge) -> Bool { 54 | return lhs.start == rhs.start && lhs.end == rhs.end 55 | } 56 | } 57 | 58 | extension Edge:Codable {} 59 | -------------------------------------------------------------------------------- /RazeMindCore/Sources/RazeMindCore/Model/Mesh+Demo.swift: -------------------------------------------------------------------------------- 1 | /// Copyright (c) 2020 Razeware LLC 2 | /// 3 | /// Permission is hereby granted, free of charge, to any person obtaining a copy 4 | /// of this software and associated documentation files (the "Software"), to deal 5 | /// in the Software without restriction, including without limitation the rights 6 | /// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | /// copies of the Software, and to permit persons to whom the Software is 8 | /// furnished to do so, subject to the following conditions: 9 | /// 10 | /// The above copyright notice and this permission notice shall be included in 11 | /// all copies or substantial portions of the Software. 12 | /// 13 | /// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish, 14 | /// distribute, sublicense, create a derivative work, and/or sell copies of the 15 | /// Software in any work that is designed, intended, or marketed for pedagogical or 16 | /// instructional purposes related to programming, coding, application development, 17 | /// or information technology. Permission for such use, copying, modification, 18 | /// merger, publication, distribution, sublicensing, creation of derivative works, 19 | /// or sale is expressly withheld. 20 | /// 21 | /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | /// THE SOFTWARE. 28 | 29 | import Foundation 30 | import CoreGraphics 31 | 32 | public extension Mesh { 33 | static func sampleMesh() -> Mesh { 34 | let mesh = Mesh() 35 | mesh.updateNodeText(mesh.rootNode(), string: "every human has a right to") 36 | [(0, "shelter"), 37 | (120, "food"), 38 | (240, "education")].forEach({ (angle, name) in 39 | let point = mesh.pointWithCenter(center: .zero, radius: 200, angle: angle.radians) 40 | let node = mesh.addChild(mesh.rootNode(), at: point) 41 | mesh.updateNodeText(node, string: name) 42 | }) 43 | return mesh 44 | } 45 | 46 | static func sampleProceduralMesh() -> Mesh { 47 | let mesh = Mesh() 48 | //seed root node with 3 children 49 | [0, 1, 2, 3].forEach({ index in 50 | let point = mesh.pointWithCenter(center: .zero, radius: 300, angle: (index * 90 + 30).radians) 51 | let node = mesh.addChild(mesh.rootNode(), at: point) 52 | mesh.updateNodeText(node, string: "A\(index + 1)") 53 | mesh.addChildrenRecursive(to: node, distance: 100, generation: 1) 54 | }) 55 | return mesh 56 | } 57 | 58 | func addChildrenRecursive(to node: Node, distance: CGFloat, generation: Int) { 59 | let labels = ["A", "B", "C", "D", "E", "F"] 60 | guard generation < labels.count else { 61 | return 62 | } 63 | 64 | let childCount = Int.random(in: 1..<4) 65 | var count = 0 66 | while count < childCount { 67 | count += 1 68 | let position = positionForNewChild(node, length: distance) 69 | let child = addChild(node, at: position) 70 | updateNodeText(child, string: "\(labels[generation])\(count + 1)") 71 | addChildrenRecursive(to: child, distance: distance + 100.0, generation: generation + 1) 72 | } 73 | } 74 | 75 | } 76 | 77 | extension Int { 78 | var radians: CGFloat { 79 | CGFloat(self) * CGFloat.pi/180.0 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /RazeMindCore/Sources/RazeMindCore/Model/Mesh+MathHelp.swift: -------------------------------------------------------------------------------- 1 | /// Copyright (c) 2020 Razeware LLC 2 | /// 3 | /// Permission is hereby granted, free of charge, to any person obtaining a copy 4 | /// of this software and associated documentation files (the "Software"), to deal 5 | /// in the Software without restriction, including without limitation the rights 6 | /// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | /// copies of the Software, and to permit persons to whom the Software is 8 | /// furnished to do so, subject to the following conditions: 9 | /// 10 | /// The above copyright notice and this permission notice shall be included in 11 | /// all copies or substantial portions of the Software. 12 | /// 13 | /// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish, 14 | /// distribute, sublicense, create a derivative work, and/or sell copies of the 15 | /// Software in any work that is designed, intended, or marketed for pedagogical or 16 | /// instructional purposes related to programming, coding, application development, 17 | /// or information technology. Permission for such use, copying, modification, 18 | /// merger, publication, distribution, sublicensing, creation of derivative works, 19 | /// or sale is expressly withheld. 20 | /// 21 | /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | /// THE SOFTWARE. 28 | 29 | import Foundation 30 | import CoreGraphics 31 | 32 | extension Mesh { 33 | 34 | public func positionForNewChild(_ parent: Node, length: CGFloat) -> CGPoint { 35 | 36 | let childEdges = edges.filter({ $0.start == parent.id }) 37 | if let grandparentedge = edges.filter({ $0.end == parent.id }).first, let grandparent = nodeWithID(grandparentedge.start) { 38 | 39 | let baseAngle = angleFrom(start: grandparent.position, end: parent.position) 40 | let childBasedAngle = positionForChildAtIndex(childEdges.count, baseAngle: baseAngle) 41 | let newpoint = pointWithCenter(center: parent.position, radius: length, angle: childBasedAngle) 42 | return newpoint 43 | } 44 | return CGPoint(x: 200, y: 200) 45 | } 46 | 47 | func positionForChildAtIndex(_ index: Int, baseAngle: CGFloat) -> CGFloat { 48 | let jitter = CGFloat.random(in: CGFloat(-1.0)...CGFloat(1.0)) * CGFloat.pi/32.0 49 | guard index > 0 else { return baseAngle + jitter } 50 | 51 | let level = (index + 1)/2 52 | let polarity: CGFloat = index % 2 == 0 ? -1.0:1.0 53 | 54 | let delta = CGFloat.pi/6.0 + jitter 55 | return baseAngle + polarity * delta * CGFloat(level) 56 | } 57 | 58 | /// angle in radians 59 | func pointWithCenter(center: CGPoint, radius: CGFloat, angle: CGFloat) -> CGPoint { 60 | let deltax = radius*cos(angle) 61 | let deltay = radius*sin(angle) 62 | let newpoint = CGPoint(x: center.x + deltax, y: center.y + deltay) 63 | return newpoint 64 | } 65 | 66 | func angleFrom(start: CGPoint, end: CGPoint) -> CGFloat { 67 | var deltax = end.x - start.x 68 | let deltay = end.y - start.y 69 | if abs(deltax) < 0.001 { 70 | deltax = 0.001 71 | } 72 | let angle = atan(deltay/abs(deltax)) 73 | return deltax > 0 ? angle: CGFloat.pi - angle 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /RazeMindCore/Sources/RazeMindCore/Model/Mesh+Storage.swift: -------------------------------------------------------------------------------- 1 | //MIT License 2 | // 3 | //Copyright (c) [2020] [Warren Burton] 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 | 23 | import Foundation 24 | 25 | /// detach storage from Mesh logic 26 | public struct MeshStorageProxy: Codable { 27 | var nodes: [Node] 28 | var edges: [Edge] 29 | var rootNodeID: NodeID 30 | } 31 | 32 | 33 | public extension Mesh { 34 | var storageObject: MeshStorageProxy { 35 | return MeshStorageProxy(nodes: nodes, edges: edges, rootNodeID: rootNodeID) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /RazeMindCore/Sources/RazeMindCore/Model/Mesh.swift: -------------------------------------------------------------------------------- 1 | /// Copyright (c) 2020 Razeware LLC 2 | /// 3 | /// Permission is hereby granted, free of charge, to any person obtaining a copy 4 | /// of this software and associated documentation files (the "Software"), to deal 5 | /// in the Software without restriction, including without limitation the rights 6 | /// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | /// copies of the Software, and to permit persons to whom the Software is 8 | /// furnished to do so, subject to the following conditions: 9 | /// 10 | /// The above copyright notice and this permission notice shall be included in 11 | /// all copies or substantial portions of the Software. 12 | /// 13 | /// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish, 14 | /// distribute, sublicense, create a derivative work, and/or sell copies of the 15 | /// Software in any work that is designed, intended, or marketed for pedagogical or 16 | /// instructional purposes related to programming, coding, application development, 17 | /// or information technology. Permission for such use, copying, modification, 18 | /// merger, publication, distribution, sublicensing, creation of derivative works, 19 | /// or sale is expressly withheld. 20 | /// 21 | /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | /// THE SOFTWARE. 28 | 29 | import Foundation 30 | import CoreGraphics 31 | 32 | public class Mesh: ObservableObject { 33 | 34 | let rootNodeID: NodeID 35 | @Published public var nodes = [Node]() 36 | @Published public var links = [EdgeProxy]() 37 | 38 | @Published public var editingText: String 39 | 40 | 41 | public init() { 42 | editingText = "" 43 | let root = Node(text: "root") 44 | rootNodeID = root.id 45 | addNode(root) 46 | } 47 | 48 | public init(storage: MeshStorageProxy) { 49 | editingText = "" 50 | rootNodeID = storage.rootNodeID 51 | nodes = storage.nodes 52 | edges = storage.edges 53 | rebuildLinks() 54 | } 55 | 56 | var edges = [Edge]() { 57 | didSet { 58 | rebuildLinks() 59 | } 60 | } 61 | 62 | func rebuildLinks() { 63 | links = edges.compactMap({ edge in 64 | let snode = nodes.filter({ $0.id == edge.start }).first 65 | let enode = nodes.filter({ $0.id == edge.end }).first 66 | if let snode = snode, let enode = enode { 67 | return EdgeProxy(id: edge.id, start: snode.position, end: enode.position) 68 | } 69 | return nil 70 | }) 71 | } 72 | 73 | public func rootNode() -> Node { 74 | guard let root = nodes.filter({ $0.id == rootNodeID }).first else { 75 | fatalError("mesh is invalid - no root") 76 | } 77 | return root 78 | } 79 | 80 | func nodeWithID(_ nodeID: NodeID) -> Node? { 81 | return nodes.filter({ $0.id == nodeID }).first 82 | } 83 | 84 | func replace(_ node: Node, with replacement: Node) { 85 | var newSet = nodes.filter({ $0.id != node.id }) 86 | newSet.append(replacement) 87 | nodes = newSet 88 | } 89 | 90 | } 91 | 92 | 93 | 94 | public extension Mesh { 95 | 96 | func updateNodeText(_ srcNode: Node, string: String) { 97 | var newNode = srcNode 98 | newNode.text = string 99 | replace(srcNode, with: newNode) 100 | } 101 | 102 | func positionNode(_ node: Node, position: CGPoint) { 103 | var movedNode = node 104 | movedNode.position = position 105 | replace(node, with: movedNode) 106 | rebuildLinks() 107 | } 108 | 109 | func processNodeTranslation(_ translation: CGSize, nodes: [DragInfo], snapToGrid: Bool = false) { 110 | nodes.forEach({ draginfo in 111 | if let node = nodeWithID(draginfo.id) { 112 | var nextPosition = draginfo.originalPosition.translatedBy(x: translation.width, y: translation.height) 113 | if snapToGrid { 114 | let granularity: CGFloat = 10.0 115 | nextPosition.x = (nextPosition.x/granularity).rounded(.toNearestOrEven) * granularity 116 | nextPosition.y = (nextPosition.y/granularity).rounded(.toNearestOrEven) * granularity 117 | } 118 | positionNode(node, position: nextPosition) 119 | } 120 | }) 121 | } 122 | 123 | func roundToTens(x : Double) -> Int { 124 | return 10 * Int(round(x / 10.0)) 125 | } 126 | 127 | } 128 | 129 | public extension Mesh { 130 | 131 | func addNode(_ node: Node) { 132 | nodes.append(node) 133 | } 134 | 135 | func connect(_ parent: Node, to child: Node) { 136 | 137 | let newedge = Edge(start: parent.id, end: child.id) 138 | let exists = edges.contains(where: { edge in 139 | return newedge == edge 140 | }) 141 | 142 | guard exists == false else { 143 | return 144 | } 145 | 146 | edges.append(newedge) 147 | } 148 | } 149 | 150 | public extension Mesh { 151 | 152 | @discardableResult func addChild(_ parent: Node, at point: CGPoint? = nil) -> Node { 153 | let target = point ?? parent.position 154 | let child = Node(position: target, text: "child") 155 | addNode(child) 156 | connect(parent, to: child) 157 | rebuildLinks() 158 | return child 159 | } 160 | 161 | @discardableResult func addSibling(_ node: Node, at point: CGPoint? = nil) -> Node? { 162 | 163 | guard node.id != rootNodeID else { 164 | return nil 165 | } 166 | 167 | let parentedges = edges.filter({ $0.end == node.id }) 168 | if let parentedge = parentedges.first, 169 | let parentnode = nodeWithID(parentedge.start) { 170 | let sibling = addChild(parentnode, at: point) 171 | return sibling 172 | } 173 | return nil 174 | } 175 | 176 | func deleteNodes(_ nodesToDelete: [NodeID]) { 177 | for id in nodesToDelete where id != rootNodeID { 178 | if let delete = nodes.firstIndex(where: { $0.id == id }) { 179 | nodes.remove(at: delete) 180 | let remainingEdges = edges.filter({ $0.end != id && $0.start != id }) 181 | edges = remainingEdges 182 | } 183 | } 184 | rebuildLinks() 185 | } 186 | 187 | func deleteNodes(_ nodesToDelete: [Node]) { 188 | deleteNodes(nodesToDelete.map({ $0.id })) 189 | } 190 | 191 | } 192 | 193 | public extension Mesh { 194 | 195 | func locateParent(_ node: Node) -> Node? { 196 | let parentedges = edges.filter({ $0.end == node.id }) 197 | if let parentedge = parentedges.first, 198 | let parentnode = nodeWithID(parentedge.start) { 199 | return parentnode 200 | } 201 | return nil 202 | } 203 | 204 | func distanceFromRoot(_ node: Node, distance: Int = 0) -> Int? { 205 | 206 | if node.id == rootNodeID { return distance } 207 | 208 | if let ancestor = locateParent(node) { 209 | if ancestor.id == rootNodeID { 210 | return distance + 1 211 | } else { 212 | return distanceFromRoot(ancestor, distance: distance + 1) 213 | } 214 | } 215 | return nil 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /RazeMindCore/Sources/RazeMindCore/Model/Node.swift: -------------------------------------------------------------------------------- 1 | /// Copyright (c) 2020 Razeware LLC 2 | /// 3 | /// Permission is hereby granted, free of charge, to any person obtaining a copy 4 | /// of this software and associated documentation files (the "Software"), to deal 5 | /// in the Software without restriction, including without limitation the rights 6 | /// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | /// copies of the Software, and to permit persons to whom the Software is 8 | /// furnished to do so, subject to the following conditions: 9 | /// 10 | /// The above copyright notice and this permission notice shall be included in 11 | /// all copies or substantial portions of the Software. 12 | /// 13 | /// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish, 14 | /// distribute, sublicense, create a derivative work, and/or sell copies of the 15 | /// Software in any work that is designed, intended, or marketed for pedagogical or 16 | /// instructional purposes related to programming, coding, application development, 17 | /// or information technology. Permission for such use, copying, modification, 18 | /// merger, publication, distribution, sublicensing, creation of derivative works, 19 | /// or sale is expressly withheld. 20 | /// 21 | /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | /// THE SOFTWARE. 28 | 29 | import Foundation 30 | import CoreGraphics 31 | 32 | public typealias NodeID = UUID 33 | 34 | public struct Node: Identifiable { 35 | public var id: NodeID = NodeID() 36 | public var position: CGPoint = .zero 37 | public var text: String = "" 38 | 39 | public init(id: NodeID = NodeID(), position: CGPoint = .zero, text: String) { 40 | self.id = id 41 | self.position = position 42 | self.text = text 43 | } 44 | 45 | public var visualID: String { 46 | return id.uuidString 47 | + "\(text.hashValue)" 48 | } 49 | 50 | } 51 | 52 | public extension Node { 53 | static func == (lhs: Node, rhs: Node) -> Bool { 54 | return lhs.id == rhs.id 55 | } 56 | } 57 | 58 | extension Node: Codable {} 59 | -------------------------------------------------------------------------------- /RazeMindCore/Sources/RazeMindCore/Model/RazeMindCore.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct RazeMindCore { 4 | public let version = "1.0" 5 | } 6 | -------------------------------------------------------------------------------- /RazeMindCore/Sources/RazeMindCore/Model/SelectionHandler.swift: -------------------------------------------------------------------------------- 1 | /// Copyright (c) 2020 Razeware LLC 2 | /// 3 | /// Permission is hereby granted, free of charge, to any person obtaining a copy 4 | /// of this software and associated documentation files (the "Software"), to deal 5 | /// in the Software without restriction, including without limitation the rights 6 | /// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | /// copies of the Software, and to permit persons to whom the Software is 8 | /// furnished to do so, subject to the following conditions: 9 | /// 10 | /// The above copyright notice and this permission notice shall be included in 11 | /// all copies or substantial portions of the Software. 12 | /// 13 | /// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish, 14 | /// distribute, sublicense, create a derivative work, and/or sell copies of the 15 | /// Software in any work that is designed, intended, or marketed for pedagogical or 16 | /// instructional purposes related to programming, coding, application development, 17 | /// or information technology. Permission for such use, copying, modification, 18 | /// merger, publication, distribution, sublicensing, creation of derivative works, 19 | /// or sale is expressly withheld. 20 | /// 21 | /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | /// THE SOFTWARE. 28 | 29 | import Foundation 30 | import CoreGraphics 31 | 32 | public struct DragInfo { 33 | var id: NodeID 34 | var originalPosition: CGPoint 35 | } 36 | 37 | public class SelectionHandler: ObservableObject { 38 | 39 | @Published public var draggingNodes = [DragInfo]() 40 | @Published public var editingText: String = "" 41 | 42 | @Published private(set) var selectedNodeIDs = [NodeID]() 43 | 44 | public init( 45 | draggingNodes: [DragInfo] = [DragInfo](), 46 | selectedNodeIDs: [NodeID] = [NodeID](), 47 | editingText: String = "" 48 | ) { 49 | self.draggingNodes = draggingNodes 50 | self.selectedNodeIDs = selectedNodeIDs 51 | self.editingText = editingText 52 | } 53 | 54 | public func selectNode(_ node: Node) { 55 | selectedNodeIDs = [node.id] 56 | editingText = node.text 57 | } 58 | 59 | public func isNodeSelected(_ node: Node) -> Bool { 60 | return selectedNodeIDs.contains(node.id) 61 | } 62 | 63 | public func selectedNodes(in mesh: Mesh) -> [Node] { 64 | return selectedNodeIDs.compactMap({ mesh.nodeWithID($0) }) 65 | } 66 | 67 | public func onlySelectedNode(in mesh: Mesh) -> Node? { 68 | let selectedNodes = self.selectedNodes(in: mesh) 69 | if selectedNodes.count == 1 { 70 | return selectedNodes.first 71 | } 72 | return nil 73 | } 74 | 75 | public func startDragging(_ mesh: Mesh) { 76 | draggingNodes = selectedNodes(in: mesh) 77 | .map({ DragInfo(id: $0.id, originalPosition: $0.position) }) 78 | } 79 | 80 | public func stopDragging(_ mesh: Mesh) { 81 | draggingNodes = [] 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /RazeMindCore/Sources/RazeMindCore/View Stack/EdgeMapView.swift: -------------------------------------------------------------------------------- 1 | /// Copyright (c) 2020 Razeware LLC 2 | /// 3 | /// Permission is hereby granted, free of charge, to any person obtaining a copy 4 | /// of this software and associated documentation files (the "Software"), to deal 5 | /// in the Software without restriction, including without limitation the rights 6 | /// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | /// copies of the Software, and to permit persons to whom the Software is 8 | /// furnished to do so, subject to the following conditions: 9 | /// 10 | /// The above copyright notice and this permission notice shall be included in 11 | /// all copies or substantial portions of the Software. 12 | /// 13 | /// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish, 14 | /// distribute, sublicense, create a derivative work, and/or sell copies of the 15 | /// Software in any work that is designed, intended, or marketed for pedagogical or 16 | /// instructional purposes related to programming, coding, application development, 17 | /// or information technology. Permission for such use, copying, modification, 18 | /// merger, publication, distribution, sublicensing, creation of derivative works, 19 | /// or sale is expressly withheld. 20 | /// 21 | /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | /// THE SOFTWARE. 28 | 29 | import SwiftUI 30 | 31 | struct EdgeMapView: View { 32 | 33 | @Binding var edges: [EdgeProxy] 34 | 35 | var body: some View { 36 | ZStack { 37 | ForEach(self.edges) { edge in 38 | EdgeView(edge: edge) 39 | .stroke(Color.black, lineWidth: 3.0) 40 | } 41 | } 42 | } 43 | 44 | } 45 | 46 | struct EdgeMapView_Previews: PreviewProvider { 47 | 48 | static let proxy1 = EdgeProxy(id: EdgeID(), 49 | start: .zero, 50 | end: CGPoint(x: -100, y: 30)) 51 | static let proxy2 = EdgeProxy(id: EdgeID(), 52 | start: .zero, 53 | end: CGPoint(x: 100, y: 30)) 54 | 55 | @State static var edges = [proxy1, proxy2] 56 | 57 | static var previews: some View { 58 | EdgeMapView(edges: $edges) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /RazeMindCore/Sources/RazeMindCore/View Stack/EdgeView.swift: -------------------------------------------------------------------------------- 1 | /// Copyright (c) 2020 Razeware LLC 2 | /// 3 | /// Permission is hereby granted, free of charge, to any person obtaining a copy 4 | /// of this software and associated documentation files (the "Software"), to deal 5 | /// in the Software without restriction, including without limitation the rights 6 | /// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | /// copies of the Software, and to permit persons to whom the Software is 8 | /// furnished to do so, subject to the following conditions: 9 | /// 10 | /// The above copyright notice and this permission notice shall be included in 11 | /// all copies or substantial portions of the Software. 12 | /// 13 | /// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish, 14 | /// distribute, sublicense, create a derivative work, and/or sell copies of the 15 | /// Software in any work that is designed, intended, or marketed for pedagogical or 16 | /// instructional purposes related to programming, coding, application development, 17 | /// or information technology. Permission for such use, copying, modification, 18 | /// merger, publication, distribution, sublicensing, creation of derivative works, 19 | /// or sale is expressly withheld. 20 | /// 21 | /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | /// THE SOFTWARE. 28 | 29 | import SwiftUI 30 | 31 | typealias AnimatableCorners = AnimatablePair< 32 | AnimatablePair, 33 | AnimatablePair> 34 | 35 | 36 | 37 | 38 | struct EdgeView: Shape { 39 | var startx: CGFloat = 0 40 | var starty: CGFloat = 0 41 | var endx: CGFloat = 0 42 | var endy: CGFloat = 0 43 | 44 | init(edge: EdgeProxy) { 45 | startx = edge.start.x 46 | starty = edge.start.y 47 | endx = edge.end.x 48 | endy = edge.end.y 49 | } 50 | 51 | func path(in rect: CGRect) -> Path { 52 | var linkPath = Path() 53 | linkPath.move(to: CGPoint(x: startx, y: starty) 54 | .alignCenterInParent(rect.size)) 55 | linkPath.addLine(to: CGPoint(x: endx, y:endy) 56 | .alignCenterInParent(rect.size)) 57 | return linkPath 58 | } 59 | 60 | var animatableData: AnimatableCorners { 61 | get { 62 | return AnimatablePair(AnimatablePair(startx, starty), 63 | AnimatablePair(endx, endy)) 64 | } 65 | set { 66 | startx = newValue.first.first 67 | starty = newValue.first.second 68 | endx = newValue.second.first 69 | endy = newValue.second.second 70 | } 71 | } 72 | } 73 | 74 | struct EdgeView_Previews: PreviewProvider { 75 | static var previews: some View { 76 | let edge1 = EdgeProxy(id: UUID(), 77 | start: CGPoint(x: -100, y: -100), 78 | end: CGPoint(x: 100, y: 100)) 79 | let edge2 = EdgeProxy(id: UUID(), 80 | start: CGPoint(x: 100, y: -100), 81 | end: CGPoint(x: -100, y: 100)) 82 | return ZStack { 83 | EdgeView(edge: edge1).stroke(lineWidth: 4) 84 | EdgeView(edge: edge2).stroke(Color.blue, lineWidth: 2) 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /RazeMindCore/Sources/RazeMindCore/View Stack/MapView.swift: -------------------------------------------------------------------------------- 1 | /// Copyright (c) 2020 Razeware LLC 2 | /// 3 | /// Permission is hereby granted, free of charge, to any person obtaining a copy 4 | /// of this software and associated documentation files (the "Software"), to deal 5 | /// in the Software without restriction, including without limitation the rights 6 | /// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | /// copies of the Software, and to permit persons to whom the Software is 8 | /// furnished to do so, subject to the following conditions: 9 | /// 10 | /// The above copyright notice and this permission notice shall be included in 11 | /// all copies or substantial portions of the Software. 12 | /// 13 | /// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish, 14 | /// distribute, sublicense, create a derivative work, and/or sell copies of the 15 | /// Software in any work that is designed, intended, or marketed for pedagogical or 16 | /// instructional purposes related to programming, coding, application development, 17 | /// or information technology. Permission for such use, copying, modification, 18 | /// merger, publication, distribution, sublicensing, creation of derivative works, 19 | /// or sale is expressly withheld. 20 | /// 21 | /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | /// THE SOFTWARE. 28 | 29 | import SwiftUI 30 | 31 | struct MapView: View { 32 | 33 | @ObservedObject var selection: SelectionHandler 34 | @ObservedObject var mesh: Mesh 35 | 36 | var body: some View { 37 | ZStack { 38 | EdgeMapView(edges: $mesh.links) 39 | NodeMapView(selection: selection, nodes: $mesh.nodes) 40 | } 41 | } 42 | } 43 | 44 | struct MapView_Previews: PreviewProvider { 45 | static var previews: some View { 46 | let mesh = Mesh() 47 | let child1 = Node(position: CGPoint(x: 100, y: 200), text: "child 1") 48 | let child2 = Node(position: CGPoint(x: -100, y: 200), text: "child 2") 49 | [child1, child2].forEach { 50 | mesh.addNode($0) 51 | mesh.connect(mesh.rootNode(), to: $0) 52 | } 53 | mesh.connect(child1, to: child2) 54 | let selection = SelectionHandler() 55 | return MapView(selection: selection, mesh: mesh) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /RazeMindCore/Sources/RazeMindCore/View Stack/NodeMapView.swift: -------------------------------------------------------------------------------- 1 | /// Copyright (c) 2020 Razeware LLC 2 | /// 3 | /// Permission is hereby granted, free of charge, to any person obtaining a copy 4 | /// of this software and associated documentation files (the "Software"), to deal 5 | /// in the Software without restriction, including without limitation the rights 6 | /// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | /// copies of the Software, and to permit persons to whom the Software is 8 | /// furnished to do so, subject to the following conditions: 9 | /// 10 | /// The above copyright notice and this permission notice shall be included in 11 | /// all copies or substantial portions of the Software. 12 | /// 13 | /// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish, 14 | /// distribute, sublicense, create a derivative work, and/or sell copies of the 15 | /// Software in any work that is designed, intended, or marketed for pedagogical or 16 | /// instructional purposes related to programming, coding, application development, 17 | /// or information technology. Permission for such use, copying, modification, 18 | /// merger, publication, distribution, sublicensing, creation of derivative works, 19 | /// or sale is expressly withheld. 20 | /// 21 | /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | /// THE SOFTWARE. 28 | 29 | import SwiftUI 30 | 31 | struct NodeMapView: View { 32 | 33 | @ObservedObject var selection: SelectionHandler 34 | @Binding var nodes: [Node] 35 | 36 | var body: some View { 37 | ZStack { 38 | ForEach(self.nodes, id: \.visualID) { node in 39 | NodeView(node: node, selection: self.selection) 40 | .offset(x: node.position.x, y: node.position.y) 41 | .onTapGesture { 42 | self.selection.selectNode(node) 43 | } 44 | } 45 | } 46 | } 47 | } 48 | 49 | struct NodeMapView_Previews: PreviewProvider { 50 | 51 | static let node1 = Node(position: CGPoint(x: -100, y: -30), text: "hello") 52 | static let node2 = Node(position: CGPoint(x: 100, y: 30), text: "world") 53 | @State static var nodes = [node1, node2] 54 | 55 | static var previews: some View { 56 | let selection = SelectionHandler() 57 | return NodeMapView(selection: selection, nodes: $nodes) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /RazeMindCore/Sources/RazeMindCore/View Stack/NodeView.swift: -------------------------------------------------------------------------------- 1 | /// Copyright (c) 2020 Razeware LLC 2 | /// 3 | /// Permission is hereby granted, free of charge, to any person obtaining a copy 4 | /// of this software and associated documentation files (the "Software"), to deal 5 | /// in the Software without restriction, including without limitation the rights 6 | /// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | /// copies of the Software, and to permit persons to whom the Software is 8 | /// furnished to do so, subject to the following conditions: 9 | /// 10 | /// The above copyright notice and this permission notice shall be included in 11 | /// all copies or substantial portions of the Software. 12 | /// 13 | /// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish, 14 | /// distribute, sublicense, create a derivative work, and/or sell copies of the 15 | /// Software in any work that is designed, intended, or marketed for pedagogical or 16 | /// instructional purposes related to programming, coding, application development, 17 | /// or information technology. Permission for such use, copying, modification, 18 | /// merger, publication, distribution, sublicensing, creation of derivative works, 19 | /// or sale is expressly withheld. 20 | /// 21 | /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | /// THE SOFTWARE. 28 | 29 | import SwiftUI 30 | 31 | struct NodeView: View { 32 | 33 | static let width = CGFloat(100) 34 | @State var node: Node 35 | @ObservedObject var selection: SelectionHandler 36 | 37 | var selected: Bool { 38 | return selection.isNodeSelected(node) 39 | } 40 | 41 | var body: some View { 42 | Rectangle() 43 | .fill(Color(UIColor.systemBackground)) 44 | .overlay( 45 | RoundedRectangle(cornerRadius: 10) 46 | .stroke(selected ? Color.red : Color.black, lineWidth: selected ? 5:3) 47 | ) 48 | .overlay(Text(node.text) 49 | .multilineTextAlignment(.center) 50 | .padding(EdgeInsets(top: 0, leading: 8, bottom: 0, trailing: 8))) 51 | .frame(width: NodeView.width, height: NodeView.width, alignment: .center) 52 | } 53 | } 54 | 55 | struct NodeView_Previews: PreviewProvider { 56 | static var previews: some View { 57 | let selection1 = SelectionHandler() 58 | let node1 = Node(text: "hello world") 59 | let selection2 = SelectionHandler() 60 | let node2 = Node(text: "I'm selected, look at me") 61 | selection2.selectNode(node2) 62 | 63 | return VStack { 64 | NodeView(node: node1, selection: selection1) 65 | NodeView(node: node2, selection: selection2) 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /RazeMindCore/Sources/RazeMindCore/View Stack/SurfaceView.swift: -------------------------------------------------------------------------------- 1 | /// Copyright (c) 2020 Razeware LLC 2 | /// 3 | /// Permission is hereby granted, free of charge, to any person obtaining a copy 4 | /// of this software and associated documentation files (the "Software"), to deal 5 | /// in the Software without restriction, including without limitation the rights 6 | /// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | /// copies of the Software, and to permit persons to whom the Software is 8 | /// furnished to do so, subject to the following conditions: 9 | /// 10 | /// The above copyright notice and this permission notice shall be included in 11 | /// all copies or substantial portions of the Software. 12 | /// 13 | /// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish, 14 | /// distribute, sublicense, create a derivative work, and/or sell copies of the 15 | /// Software in any work that is designed, intended, or marketed for pedagogical or 16 | /// instructional purposes related to programming, coding, application development, 17 | /// or information technology. Permission for such use, copying, modification, 18 | /// merger, publication, distribution, sublicensing, creation of derivative works, 19 | /// or sale is expressly withheld. 20 | /// 21 | /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | /// THE SOFTWARE. 28 | 29 | import SwiftUI 30 | 31 | public struct SurfaceView: View { 32 | 33 | @ObservedObject var mesh: Mesh 34 | @ObservedObject var selection: SelectionHandler 35 | 36 | //dragging 37 | @State var portalPosition: CGPoint = .zero 38 | @State var dragOffset: CGSize = .zero 39 | @State var isDragging: Bool = false 40 | @State var isDraggingMesh: Bool = false 41 | 42 | //zooming 43 | @State var zoomScale: CGFloat = 1.0 44 | @State var initialZoomScale: CGFloat? 45 | @State var initialPortalPosition: CGPoint? 46 | 47 | public init( 48 | mesh: Mesh, 49 | selection: SelectionHandler, 50 | portalPosition: CGPoint = .zero, 51 | dragOffset: CGSize = .zero, 52 | isDragging: Bool = false, 53 | isDraggingMesh: Bool = false, 54 | zoomScale: CGFloat = 1.0, 55 | initialZoomScale: CGFloat? = nil, 56 | initialPortalPosition: CGPoint? = nil 57 | ) { 58 | self.mesh = mesh 59 | self.selection = selection 60 | self.portalPosition = portalPosition 61 | self.dragOffset = dragOffset 62 | self.isDragging = isDragging 63 | self.isDraggingMesh = isDraggingMesh 64 | self.zoomScale = zoomScale 65 | self.initialZoomScale = initialZoomScale 66 | self.initialPortalPosition = initialPortalPosition 67 | } 68 | 69 | public var body: some View { 70 | VStack { 71 | GeometryReader { geometry in 72 | ZStack { 73 | Rectangle().fill(Color(UIColor.systemBackground)) 74 | MapView(selection: self.selection, mesh: self.mesh) 75 | .scaleEffect(self.zoomScale) 76 | .offset(x: self.portalPosition.x + self.dragOffset.width, 77 | y: self.portalPosition.y + self.dragOffset.height) 78 | .animation(.easeIn, value: portalPosition) 79 | } 80 | .drawingGroup(opaque: true, colorMode: .extendedLinear) 81 | .gesture(DragGesture() 82 | .onChanged { value in 83 | self.processDragChange(value, containerSize: geometry.size) 84 | } 85 | .onEnded { value in 86 | self.processDragEnd(value) 87 | }) 88 | .gesture(MagnificationGesture() 89 | .onChanged { value in 90 | if self.initialZoomScale == nil { 91 | self.initialZoomScale = self.zoomScale 92 | self.initialPortalPosition = self.portalPosition 93 | } 94 | self.processScaleChange(value) 95 | 96 | } 97 | .onEnded { value in 98 | self.processScaleChange(value) 99 | self.initialZoomScale = nil 100 | self.initialPortalPosition = nil 101 | }) 102 | } 103 | } 104 | } 105 | } 106 | 107 | struct SurfaceView_Previews: PreviewProvider { 108 | static var previews: some View { 109 | let mesh = Mesh.sampleProceduralMesh() 110 | let selection = SelectionHandler() 111 | return SurfaceView(mesh: mesh, selection: selection) 112 | } 113 | } 114 | 115 | extension SurfaceView { 116 | 117 | // 1 118 | func distanceFrom(_ pointA: CGPoint, to pointB: CGPoint) -> CGFloat { 119 | let xdelta = pow(pointA.x - pointB.x,2) 120 | let ydelta = pow(pointA.y - pointB.y,2) 121 | 122 | return sqrt(xdelta + ydelta) 123 | } 124 | 125 | // 2 126 | func hitTest(point: CGPoint, parent: CGSize) -> Node? { 127 | for node in mesh.nodes { 128 | 129 | let endPoint = node.position 130 | .scaledFrom(self.zoomScale) 131 | .alignCenterInParent(parent) 132 | .translatedBy(x: portalPosition.x, y: portalPosition.y) 133 | let dist = distanceFrom(point, to: endPoint) / self.zoomScale 134 | 135 | //3 136 | if dist < NodeView.width/2.0 { 137 | return node 138 | } 139 | } 140 | return nil 141 | } 142 | 143 | // 4 144 | func processNodeTranslation(_ translation: CGSize, snapToGrid: Bool = false) { 145 | guard selection.draggingNodes.isEmpty == false else { 146 | return 147 | } 148 | let scaledTranslation = translation.scaledDownTo(self.zoomScale) 149 | mesh.processNodeTranslation(scaledTranslation, 150 | nodes: selection.draggingNodes, 151 | snapToGrid: snapToGrid) 152 | } 153 | 154 | 155 | func processDragChange(_ value: DragGesture.Value, 156 | containerSize: CGSize) { 157 | // 1 158 | if isDragging == false { 159 | isDragging = true 160 | 161 | if let node = self.hitTest(point: value.startLocation, 162 | parent: containerSize) { 163 | isDraggingMesh = false 164 | selection.selectNode(node) 165 | // 2 166 | selection.startDragging(mesh) 167 | } else { 168 | isDraggingMesh = true 169 | } 170 | } 171 | 172 | // 3 173 | if isDraggingMesh { 174 | self.dragOffset = value.translation 175 | } else { 176 | processNodeTranslation(value.translation) 177 | } 178 | } 179 | 180 | // 4 181 | func processDragEnd(_ value: DragGesture.Value) { 182 | isDragging = false 183 | dragOffset = .zero 184 | 185 | if isDraggingMesh { 186 | self.portalPosition = CGPoint(x: self.portalPosition.x + value.translation.width, 187 | y: self.portalPosition.y + value.translation.height) 188 | } else { 189 | processNodeTranslation(value.translation, snapToGrid: true) 190 | selection.stopDragging(mesh) 191 | } 192 | } 193 | 194 | } 195 | 196 | extension SurfaceView { 197 | func scaledOffset(_ scale: CGFloat, initialValue: CGPoint) -> CGPoint { 198 | let newx = initialValue.x*scale 199 | let newy = initialValue.y*scale 200 | return CGPoint(x: newx, y: newy) 201 | } 202 | 203 | func clampedScale(_ scale: CGFloat, initialValue: CGFloat?) -> (scale: CGFloat, didClamp: Bool) { 204 | let minScale: CGFloat = 0.1 205 | let maxScale: CGFloat = 2.0 206 | let raw = scale.magnitude * (initialValue ?? maxScale) 207 | let value = max(minScale, min(maxScale, raw)) 208 | let didClamp = raw != value 209 | return (value, didClamp) 210 | } 211 | 212 | // 3 213 | func processScaleChange(_ value: CGFloat) { 214 | let clamped = self.clampedScale(value, initialValue: self.initialZoomScale) 215 | self.zoomScale = clamped.scale 216 | if clamped.didClamp == false, let point = self.initialPortalPosition { 217 | self.portalPosition = self.scaledOffset(value, initialValue: point) 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /RazeMindCore/Tests/RazeMindCoreTests/RazeMindCoreTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import RazeMindCore 3 | 4 | final class RazeMindCoreTests: XCTestCase { 5 | func testVersion() throws { 6 | XCTAssertEqual(RazeMindCore().version, "1.0") 7 | } 8 | } 9 | --------------------------------------------------------------------------------