├── .gitignore ├── .gitmodules ├── README.md ├── Shades.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── christopherzelazo.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── Shades ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Code Table │ ├── Cells │ │ ├── CodeCell.swift │ │ ├── GeometryCell.swift │ │ └── SettingCell.swift │ ├── CodeTableView.swift │ ├── InputToolbar.swift │ ├── Section.swift │ └── SectionHeaderView.swift ├── Extensions │ ├── Bundle+Additions.swift │ ├── Haptic.swift │ ├── Math+Additions.swift │ ├── UIKit+Additions.swift │ ├── UITableView+Additions.swift │ └── UIView+Additions.swift ├── GLSLLexer.swift ├── GLSLTheme.swift ├── Info.plist ├── Recording │ ├── Photos+Additions.swift │ └── Recorder.swift ├── SceneController.swift ├── SceneViewController.swift └── Shader.swift └── source-editor ├── .gitattributes ├── .gitignore ├── .gitmodules ├── .swift-version ├── .travis.yml ├── LICENSE ├── README.md ├── SourceEditor.podspec ├── SourceEditor.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ └── WorkspaceSettings.xcsettings ├── SourceEditor.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Sources ├── Info.plist ├── Languages │ ├── Python3Lexer.swift │ └── SwiftLexer.swift ├── SourceCodeRegexLexer.swift ├── SourceCodeTheme.swift ├── SourceCodeToken.swift ├── SourceEditor.h └── Themes │ └── DefaultSourceCodeTheme.swift ├── iOS Example ├── iOS Example.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── iOS Example.xcscheme └── iOS Example │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── ViewController.swift └── readme-resources └── ios-example.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | # 51 | # Add this line if you want to avoid checking in source code from the Xcode workspace 52 | # *.xcworkspace 53 | 54 | # Carthage 55 | # 56 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 57 | # Carthage/Checkouts 58 | 59 | Carthage/Build 60 | 61 | # Accio dependency management 62 | Dependencies/ 63 | .accio/ 64 | 65 | # fastlane 66 | # 67 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 68 | # screenshots whenever they are needed. 69 | # For more information about the recommended setup visit: 70 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 71 | 72 | fastlane/report.xml 73 | fastlane/Preview.html 74 | fastlane/screenshots/**/*.png 75 | fastlane/test_output 76 | 77 | # Code Injection 78 | # 79 | # After new code Injection tools there's a generated folder /iOSInjectionProject 80 | # https://github.com/johnno1962/injectionforxcode 81 | 82 | iOSInjectionProject/ 83 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "source-editor"] 2 | path = source-editor 3 | url = git@github.com:louisdh/source-editor.git 4 | [submodule "savannakit"] 5 | path = savannakit 6 | url = git@github.com:chriszelazo/savannakit.git 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DarthShader 2 | DarthShader is a simple fragment shader playground for iOS. A live version of the fragment shader code runs behind the edit view and is running on a SceneKit plane. 2-finger tapping will toggle the edit view and enable interaction with the SceneKit plane. The fragment shader is saved to the disk as it is edited and persists between sessions. 3 | 4 | ## Features 5 | 6 | ### Live editing 7 | ![](https://media.giphy.com/media/12wPauZmqAjxinjWVK/giphy.gif) 8 | 9 | ### Collapsable Code 10 | ![](https://media.giphy.com/media/26mdn8VnZXuuvwlNqB/giphy.gif) 11 | 12 | ### Different Geometries 13 | ![](https://media.giphy.com/media/7YCVTkNhzaWDFApolH/giphy.gif) 14 | ![](https://media.giphy.com/media/nbNWGfq0GP3Mj6Kj29/giphy.gif) 15 | 16 | 17 | ## Installation 18 | 19 | Clone project and initialize submodules: 20 | `git submodule update --init --recursive` 21 | 22 | 23 | ## Thanks 24 | 25 | - [@pgothe](https://github.com/gopatrik) 26 | - [Patricio Gonzalez Vivo](http://patriciogonzalezvivo.com/) for [The Book of Shaders](https://thebookofshaders.com/) 27 | -------------------------------------------------------------------------------- /Shades.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 8111BC822244C7A50020A1B8 /* Section.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8111BC812244C7A50020A1B8 /* Section.swift */; }; 11 | 8111BC862244CDA10020A1B8 /* SectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8111BC852244CDA10020A1B8 /* SectionHeaderView.swift */; }; 12 | 81259D5A2238E774002CFCDF /* Haptic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81259D592238E773002CFCDF /* Haptic.swift */; }; 13 | 8144D19222140562008DBF5C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8144D19122140562008DBF5C /* AppDelegate.swift */; }; 14 | 8144D19722140562008DBF5C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8144D19522140562008DBF5C /* Main.storyboard */; }; 15 | 8144D19922140564008DBF5C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8144D19822140564008DBF5C /* Assets.xcassets */; }; 16 | 8144D19C22140564008DBF5C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8144D19A22140564008DBF5C /* LaunchScreen.storyboard */; }; 17 | 8144D1A4221405A7008DBF5C /* SceneController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8144D1A3221405A7008DBF5C /* SceneController.swift */; }; 18 | 8145137D222E83BA00D799DD /* UIKit+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8145137C222E83BA00D799DD /* UIKit+Additions.swift */; }; 19 | 81451380222E849F00D799DD /* Math+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8145137F222E849F00D799DD /* Math+Additions.swift */; }; 20 | 814D878F221B747E00CED7A7 /* Shader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 814D878E221B747E00CED7A7 /* Shader.swift */; }; 21 | 814D8792221B891100CED7A7 /* Bundle+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 814D8791221B891100CED7A7 /* Bundle+Additions.swift */; }; 22 | 814D8794221B987D00CED7A7 /* SceneViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 814D8793221B987D00CED7A7 /* SceneViewController.swift */; }; 23 | 814D8796221BD77500CED7A7 /* UITableView+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 814D8795221BD77500CED7A7 /* UITableView+Additions.swift */; }; 24 | 814D8798221BD9F100CED7A7 /* CodeTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 814D8797221BD9F100CED7A7 /* CodeTableView.swift */; }; 25 | 814D879C221BDCDE00CED7A7 /* CodeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 814D879B221BDCDE00CED7A7 /* CodeCell.swift */; }; 26 | 814D87B4221BE25D00CED7A7 /* SourceEditor.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 814D87AF221BE21600CED7A7 /* SourceEditor.framework */; }; 27 | 814D87B5221BE25D00CED7A7 /* SourceEditor.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 814D87AF221BE21600CED7A7 /* SourceEditor.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 28 | 816488D02225134A00E8939E /* UIView+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 816488CF2225134A00E8939E /* UIView+Additions.swift */; }; 29 | 81648915222A1E9E00E8939E /* InputToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81648914222A1E9E00E8939E /* InputToolbar.swift */; }; 30 | 81661510224766EE00F3834D /* GeometryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8166150F224766EE00F3834D /* GeometryCell.swift */; }; 31 | 819E51A9221BF6C10012798A /* GLSLLexer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 819E51A8221BF6C10012798A /* GLSLLexer.swift */; }; 32 | 819E51AB221BFCE90012798A /* GLSLTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 819E51AA221BFCE90012798A /* GLSLTheme.swift */; }; 33 | 81AACD2C223B778200F27CCE /* SavannaKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 819E51A5221BE6F90012798A /* SavannaKit.framework */; }; 34 | 81AACD2D223B778200F27CCE /* SavannaKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 819E51A5221BE6F90012798A /* SavannaKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 35 | 81C05461225ADFB700A9EABA /* SettingCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81C05460225ADFB700A9EABA /* SettingCell.swift */; }; 36 | 81C05467225AE4AE00A9EABA /* Photos+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81C05466225AE4AE00A9EABA /* Photos+Additions.swift */; }; 37 | 81C05469225B1BAD00A9EABA /* Recorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81C05468225B1BAD00A9EABA /* Recorder.swift */; }; 38 | /* End PBXBuildFile section */ 39 | 40 | /* Begin PBXContainerItemProxy section */ 41 | 814D87AE221BE21600CED7A7 /* PBXContainerItemProxy */ = { 42 | isa = PBXContainerItemProxy; 43 | containerPortal = 814D87AA221BE21600CED7A7 /* SourceEditor.xcodeproj */; 44 | proxyType = 2; 45 | remoteGlobalIDString = BE09FBE220FFCF06002BA501; 46 | remoteInfo = SourceEditor; 47 | }; 48 | 814D87B6221BE25D00CED7A7 /* PBXContainerItemProxy */ = { 49 | isa = PBXContainerItemProxy; 50 | containerPortal = 814D87AA221BE21600CED7A7 /* SourceEditor.xcodeproj */; 51 | proxyType = 1; 52 | remoteGlobalIDString = BE09FBE120FFCF06002BA501; 53 | remoteInfo = SourceEditor; 54 | }; 55 | /* End PBXContainerItemProxy section */ 56 | 57 | /* Begin PBXCopyFilesBuildPhase section */ 58 | 814D87B8221BE25D00CED7A7 /* Embed Frameworks */ = { 59 | isa = PBXCopyFilesBuildPhase; 60 | buildActionMask = 2147483647; 61 | dstPath = ""; 62 | dstSubfolderSpec = 10; 63 | files = ( 64 | 81AACD2D223B778200F27CCE /* SavannaKit.framework in Embed Frameworks */, 65 | 814D87B5221BE25D00CED7A7 /* SourceEditor.framework in Embed Frameworks */, 66 | ); 67 | name = "Embed Frameworks"; 68 | runOnlyForDeploymentPostprocessing = 0; 69 | }; 70 | /* End PBXCopyFilesBuildPhase section */ 71 | 72 | /* Begin PBXFileReference section */ 73 | 8111BC812244C7A50020A1B8 /* Section.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Section.swift; sourceTree = ""; }; 74 | 8111BC852244CDA10020A1B8 /* SectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionHeaderView.swift; sourceTree = ""; }; 75 | 81259D592238E773002CFCDF /* Haptic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Haptic.swift; sourceTree = ""; }; 76 | 8144D18E22140562008DBF5C /* Shades.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Shades.app; sourceTree = BUILT_PRODUCTS_DIR; }; 77 | 8144D19122140562008DBF5C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 78 | 8144D19622140562008DBF5C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 79 | 8144D19822140564008DBF5C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 80 | 8144D19B22140564008DBF5C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 81 | 8144D19D22140564008DBF5C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 82 | 8144D1A3221405A7008DBF5C /* SceneController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneController.swift; sourceTree = ""; }; 83 | 8145137C222E83BA00D799DD /* UIKit+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIKit+Additions.swift"; sourceTree = ""; }; 84 | 8145137F222E849F00D799DD /* Math+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Math+Additions.swift"; sourceTree = ""; }; 85 | 814D878E221B747E00CED7A7 /* Shader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shader.swift; sourceTree = ""; }; 86 | 814D8791221B891100CED7A7 /* Bundle+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+Additions.swift"; sourceTree = ""; }; 87 | 814D8793221B987D00CED7A7 /* SceneViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneViewController.swift; sourceTree = ""; }; 88 | 814D8795221BD77500CED7A7 /* UITableView+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+Additions.swift"; sourceTree = ""; }; 89 | 814D8797221BD9F100CED7A7 /* CodeTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeTableView.swift; sourceTree = ""; }; 90 | 814D879B221BDCDE00CED7A7 /* CodeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeCell.swift; sourceTree = ""; }; 91 | 814D87AA221BE21600CED7A7 /* SourceEditor.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SourceEditor.xcodeproj; path = "source-editor/SourceEditor.xcodeproj"; sourceTree = ""; }; 92 | 816488CF2225134A00E8939E /* UIView+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Additions.swift"; sourceTree = ""; }; 93 | 81648914222A1E9E00E8939E /* InputToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputToolbar.swift; sourceTree = ""; }; 94 | 8166150F224766EE00F3834D /* GeometryCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeometryCell.swift; sourceTree = ""; }; 95 | 819E51A5221BE6F90012798A /* SavannaKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SavannaKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 96 | 819E51A8221BF6C10012798A /* GLSLLexer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GLSLLexer.swift; sourceTree = ""; }; 97 | 819E51AA221BFCE90012798A /* GLSLTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GLSLTheme.swift; sourceTree = ""; }; 98 | 81C05460225ADFB700A9EABA /* SettingCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingCell.swift; sourceTree = ""; }; 99 | 81C05466225AE4AE00A9EABA /* Photos+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Photos+Additions.swift"; sourceTree = ""; }; 100 | 81C05468225B1BAD00A9EABA /* Recorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Recorder.swift; sourceTree = ""; }; 101 | /* End PBXFileReference section */ 102 | 103 | /* Begin PBXFrameworksBuildPhase section */ 104 | 8144D18B22140562008DBF5C /* Frameworks */ = { 105 | isa = PBXFrameworksBuildPhase; 106 | buildActionMask = 2147483647; 107 | files = ( 108 | 81AACD2C223B778200F27CCE /* SavannaKit.framework in Frameworks */, 109 | 814D87B4221BE25D00CED7A7 /* SourceEditor.framework in Frameworks */, 110 | ); 111 | runOnlyForDeploymentPostprocessing = 0; 112 | }; 113 | /* End PBXFrameworksBuildPhase section */ 114 | 115 | /* Begin PBXGroup section */ 116 | 8144D18522140562008DBF5C = { 117 | isa = PBXGroup; 118 | children = ( 119 | 819E51A5221BE6F90012798A /* SavannaKit.framework */, 120 | 814D87AA221BE21600CED7A7 /* SourceEditor.xcodeproj */, 121 | 8144D19022140562008DBF5C /* Shades */, 122 | 8144D18F22140562008DBF5C /* Products */, 123 | 819E5173221BE3360012798A /* Frameworks */, 124 | ); 125 | sourceTree = ""; 126 | }; 127 | 8144D18F22140562008DBF5C /* Products */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | 8144D18E22140562008DBF5C /* Shades.app */, 131 | ); 132 | name = Products; 133 | sourceTree = ""; 134 | }; 135 | 8144D19022140562008DBF5C /* Shades */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | 81C0545A225ADF1D00A9EABA /* Recording */, 139 | 816488BA2223564700E8939E /* Code Table */, 140 | 8144D19122140562008DBF5C /* AppDelegate.swift */, 141 | 8144D1A3221405A7008DBF5C /* SceneController.swift */, 142 | 814D8793221B987D00CED7A7 /* SceneViewController.swift */, 143 | 814D878E221B747E00CED7A7 /* Shader.swift */, 144 | 814D8790221B890000CED7A7 /* Extensions */, 145 | 8144D19522140562008DBF5C /* Main.storyboard */, 146 | 8144D19822140564008DBF5C /* Assets.xcassets */, 147 | 8144D19A22140564008DBF5C /* LaunchScreen.storyboard */, 148 | 8144D19D22140564008DBF5C /* Info.plist */, 149 | 819E51A8221BF6C10012798A /* GLSLLexer.swift */, 150 | 819E51AA221BFCE90012798A /* GLSLTheme.swift */, 151 | ); 152 | path = Shades; 153 | sourceTree = ""; 154 | }; 155 | 814D8790221B890000CED7A7 /* Extensions */ = { 156 | isa = PBXGroup; 157 | children = ( 158 | 814D8791221B891100CED7A7 /* Bundle+Additions.swift */, 159 | 814D8795221BD77500CED7A7 /* UITableView+Additions.swift */, 160 | 8145137C222E83BA00D799DD /* UIKit+Additions.swift */, 161 | 816488CF2225134A00E8939E /* UIView+Additions.swift */, 162 | 8145137F222E849F00D799DD /* Math+Additions.swift */, 163 | 81259D592238E773002CFCDF /* Haptic.swift */, 164 | ); 165 | path = Extensions; 166 | sourceTree = ""; 167 | }; 168 | 814D87AB221BE21600CED7A7 /* Products */ = { 169 | isa = PBXGroup; 170 | children = ( 171 | 814D87AF221BE21600CED7A7 /* SourceEditor.framework */, 172 | ); 173 | name = Products; 174 | sourceTree = ""; 175 | }; 176 | 816488BA2223564700E8939E /* Code Table */ = { 177 | isa = PBXGroup; 178 | children = ( 179 | 814D8797221BD9F100CED7A7 /* CodeTableView.swift */, 180 | 81648914222A1E9E00E8939E /* InputToolbar.swift */, 181 | 8111BC812244C7A50020A1B8 /* Section.swift */, 182 | 8111BC852244CDA10020A1B8 /* SectionHeaderView.swift */, 183 | 81C0545F225ADFA600A9EABA /* Cells */, 184 | ); 185 | path = "Code Table"; 186 | sourceTree = ""; 187 | }; 188 | 819E5173221BE3360012798A /* Frameworks */ = { 189 | isa = PBXGroup; 190 | children = ( 191 | ); 192 | name = Frameworks; 193 | sourceTree = ""; 194 | }; 195 | 81C0545A225ADF1D00A9EABA /* Recording */ = { 196 | isa = PBXGroup; 197 | children = ( 198 | 81C05466225AE4AE00A9EABA /* Photos+Additions.swift */, 199 | 81C05468225B1BAD00A9EABA /* Recorder.swift */, 200 | ); 201 | path = Recording; 202 | sourceTree = ""; 203 | }; 204 | 81C0545F225ADFA600A9EABA /* Cells */ = { 205 | isa = PBXGroup; 206 | children = ( 207 | 814D879B221BDCDE00CED7A7 /* CodeCell.swift */, 208 | 8166150F224766EE00F3834D /* GeometryCell.swift */, 209 | 81C05460225ADFB700A9EABA /* SettingCell.swift */, 210 | ); 211 | path = Cells; 212 | sourceTree = ""; 213 | }; 214 | /* End PBXGroup section */ 215 | 216 | /* Begin PBXNativeTarget section */ 217 | 8144D18D22140562008DBF5C /* Shades */ = { 218 | isa = PBXNativeTarget; 219 | buildConfigurationList = 8144D1A022140564008DBF5C /* Build configuration list for PBXNativeTarget "Shades" */; 220 | buildPhases = ( 221 | 8144D18A22140562008DBF5C /* Sources */, 222 | 8144D18B22140562008DBF5C /* Frameworks */, 223 | 8144D18C22140562008DBF5C /* Resources */, 224 | 814D87B8221BE25D00CED7A7 /* Embed Frameworks */, 225 | ); 226 | buildRules = ( 227 | ); 228 | dependencies = ( 229 | 814D87B7221BE25D00CED7A7 /* PBXTargetDependency */, 230 | ); 231 | name = Shades; 232 | productName = Shades; 233 | productReference = 8144D18E22140562008DBF5C /* Shades.app */; 234 | productType = "com.apple.product-type.application"; 235 | }; 236 | /* End PBXNativeTarget section */ 237 | 238 | /* Begin PBXProject section */ 239 | 8144D18622140562008DBF5C /* Project object */ = { 240 | isa = PBXProject; 241 | attributes = { 242 | LastSwiftUpdateCheck = 1010; 243 | LastUpgradeCheck = 1010; 244 | ORGANIZATIONNAME = "Chris Zelazo"; 245 | TargetAttributes = { 246 | 8144D18D22140562008DBF5C = { 247 | CreatedOnToolsVersion = 10.1; 248 | }; 249 | }; 250 | }; 251 | buildConfigurationList = 8144D18922140562008DBF5C /* Build configuration list for PBXProject "Shades" */; 252 | compatibilityVersion = "Xcode 9.3"; 253 | developmentRegion = en; 254 | hasScannedForEncodings = 0; 255 | knownRegions = ( 256 | en, 257 | Base, 258 | ); 259 | mainGroup = 8144D18522140562008DBF5C; 260 | productRefGroup = 8144D18F22140562008DBF5C /* Products */; 261 | projectDirPath = ""; 262 | projectReferences = ( 263 | { 264 | ProductGroup = 814D87AB221BE21600CED7A7 /* Products */; 265 | ProjectRef = 814D87AA221BE21600CED7A7 /* SourceEditor.xcodeproj */; 266 | }, 267 | ); 268 | projectRoot = ""; 269 | targets = ( 270 | 8144D18D22140562008DBF5C /* Shades */, 271 | ); 272 | }; 273 | /* End PBXProject section */ 274 | 275 | /* Begin PBXReferenceProxy section */ 276 | 814D87AF221BE21600CED7A7 /* SourceEditor.framework */ = { 277 | isa = PBXReferenceProxy; 278 | fileType = wrapper.framework; 279 | path = SourceEditor.framework; 280 | remoteRef = 814D87AE221BE21600CED7A7 /* PBXContainerItemProxy */; 281 | sourceTree = BUILT_PRODUCTS_DIR; 282 | }; 283 | /* End PBXReferenceProxy section */ 284 | 285 | /* Begin PBXResourcesBuildPhase section */ 286 | 8144D18C22140562008DBF5C /* Resources */ = { 287 | isa = PBXResourcesBuildPhase; 288 | buildActionMask = 2147483647; 289 | files = ( 290 | 8144D19C22140564008DBF5C /* LaunchScreen.storyboard in Resources */, 291 | 8144D19922140564008DBF5C /* Assets.xcassets in Resources */, 292 | 8144D19722140562008DBF5C /* Main.storyboard in Resources */, 293 | ); 294 | runOnlyForDeploymentPostprocessing = 0; 295 | }; 296 | /* End PBXResourcesBuildPhase section */ 297 | 298 | /* Begin PBXSourcesBuildPhase section */ 299 | 8144D18A22140562008DBF5C /* Sources */ = { 300 | isa = PBXSourcesBuildPhase; 301 | buildActionMask = 2147483647; 302 | files = ( 303 | 8111BC822244C7A50020A1B8 /* Section.swift in Sources */, 304 | 81259D5A2238E774002CFCDF /* Haptic.swift in Sources */, 305 | 816488D02225134A00E8939E /* UIView+Additions.swift in Sources */, 306 | 8144D19222140562008DBF5C /* AppDelegate.swift in Sources */, 307 | 81648915222A1E9E00E8939E /* InputToolbar.swift in Sources */, 308 | 81661510224766EE00F3834D /* GeometryCell.swift in Sources */, 309 | 814D8798221BD9F100CED7A7 /* CodeTableView.swift in Sources */, 310 | 8144D1A4221405A7008DBF5C /* SceneController.swift in Sources */, 311 | 814D8794221B987D00CED7A7 /* SceneViewController.swift in Sources */, 312 | 81C05461225ADFB700A9EABA /* SettingCell.swift in Sources */, 313 | 8145137D222E83BA00D799DD /* UIKit+Additions.swift in Sources */, 314 | 81C05469225B1BAD00A9EABA /* Recorder.swift in Sources */, 315 | 814D879C221BDCDE00CED7A7 /* CodeCell.swift in Sources */, 316 | 81C05467225AE4AE00A9EABA /* Photos+Additions.swift in Sources */, 317 | 8111BC862244CDA10020A1B8 /* SectionHeaderView.swift in Sources */, 318 | 819E51AB221BFCE90012798A /* GLSLTheme.swift in Sources */, 319 | 819E51A9221BF6C10012798A /* GLSLLexer.swift in Sources */, 320 | 814D8796221BD77500CED7A7 /* UITableView+Additions.swift in Sources */, 321 | 81451380222E849F00D799DD /* Math+Additions.swift in Sources */, 322 | 814D8792221B891100CED7A7 /* Bundle+Additions.swift in Sources */, 323 | 814D878F221B747E00CED7A7 /* Shader.swift in Sources */, 324 | ); 325 | runOnlyForDeploymentPostprocessing = 0; 326 | }; 327 | /* End PBXSourcesBuildPhase section */ 328 | 329 | /* Begin PBXTargetDependency section */ 330 | 814D87B7221BE25D00CED7A7 /* PBXTargetDependency */ = { 331 | isa = PBXTargetDependency; 332 | name = SourceEditor; 333 | targetProxy = 814D87B6221BE25D00CED7A7 /* PBXContainerItemProxy */; 334 | }; 335 | /* End PBXTargetDependency section */ 336 | 337 | /* Begin PBXVariantGroup section */ 338 | 8144D19522140562008DBF5C /* Main.storyboard */ = { 339 | isa = PBXVariantGroup; 340 | children = ( 341 | 8144D19622140562008DBF5C /* Base */, 342 | ); 343 | name = Main.storyboard; 344 | sourceTree = ""; 345 | }; 346 | 8144D19A22140564008DBF5C /* LaunchScreen.storyboard */ = { 347 | isa = PBXVariantGroup; 348 | children = ( 349 | 8144D19B22140564008DBF5C /* Base */, 350 | ); 351 | name = LaunchScreen.storyboard; 352 | sourceTree = ""; 353 | }; 354 | /* End PBXVariantGroup section */ 355 | 356 | /* Begin XCBuildConfiguration section */ 357 | 8144D19E22140564008DBF5C /* Debug */ = { 358 | isa = XCBuildConfiguration; 359 | buildSettings = { 360 | ALWAYS_SEARCH_USER_PATHS = NO; 361 | CLANG_ANALYZER_NONNULL = YES; 362 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 363 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 364 | CLANG_CXX_LIBRARY = "libc++"; 365 | CLANG_ENABLE_MODULES = YES; 366 | CLANG_ENABLE_OBJC_ARC = YES; 367 | CLANG_ENABLE_OBJC_WEAK = YES; 368 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 369 | CLANG_WARN_BOOL_CONVERSION = YES; 370 | CLANG_WARN_COMMA = YES; 371 | CLANG_WARN_CONSTANT_CONVERSION = YES; 372 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 373 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 374 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 375 | CLANG_WARN_EMPTY_BODY = YES; 376 | CLANG_WARN_ENUM_CONVERSION = YES; 377 | CLANG_WARN_INFINITE_RECURSION = YES; 378 | CLANG_WARN_INT_CONVERSION = YES; 379 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 380 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 381 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 382 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 383 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 384 | CLANG_WARN_STRICT_PROTOTYPES = YES; 385 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 386 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 387 | CLANG_WARN_UNREACHABLE_CODE = YES; 388 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 389 | CODE_SIGN_IDENTITY = "iPhone Developer"; 390 | COPY_PHASE_STRIP = NO; 391 | DEBUG_INFORMATION_FORMAT = dwarf; 392 | ENABLE_STRICT_OBJC_MSGSEND = YES; 393 | ENABLE_TESTABILITY = YES; 394 | GCC_C_LANGUAGE_STANDARD = gnu11; 395 | GCC_DYNAMIC_NO_PIC = NO; 396 | GCC_NO_COMMON_BLOCKS = YES; 397 | GCC_OPTIMIZATION_LEVEL = 0; 398 | GCC_PREPROCESSOR_DEFINITIONS = ( 399 | "DEBUG=1", 400 | "$(inherited)", 401 | ); 402 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 403 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 404 | GCC_WARN_UNDECLARED_SELECTOR = YES; 405 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 406 | GCC_WARN_UNUSED_FUNCTION = YES; 407 | GCC_WARN_UNUSED_VARIABLE = YES; 408 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 409 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 410 | MTL_FAST_MATH = YES; 411 | ONLY_ACTIVE_ARCH = YES; 412 | SDKROOT = iphoneos; 413 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 414 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 415 | }; 416 | name = Debug; 417 | }; 418 | 8144D19F22140564008DBF5C /* Release */ = { 419 | isa = XCBuildConfiguration; 420 | buildSettings = { 421 | ALWAYS_SEARCH_USER_PATHS = NO; 422 | CLANG_ANALYZER_NONNULL = YES; 423 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 424 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 425 | CLANG_CXX_LIBRARY = "libc++"; 426 | CLANG_ENABLE_MODULES = YES; 427 | CLANG_ENABLE_OBJC_ARC = YES; 428 | CLANG_ENABLE_OBJC_WEAK = YES; 429 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 430 | CLANG_WARN_BOOL_CONVERSION = YES; 431 | CLANG_WARN_COMMA = YES; 432 | CLANG_WARN_CONSTANT_CONVERSION = YES; 433 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 434 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 435 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 436 | CLANG_WARN_EMPTY_BODY = YES; 437 | CLANG_WARN_ENUM_CONVERSION = YES; 438 | CLANG_WARN_INFINITE_RECURSION = YES; 439 | CLANG_WARN_INT_CONVERSION = YES; 440 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 441 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 442 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 443 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 444 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 445 | CLANG_WARN_STRICT_PROTOTYPES = YES; 446 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 447 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 448 | CLANG_WARN_UNREACHABLE_CODE = YES; 449 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 450 | CODE_SIGN_IDENTITY = "iPhone Developer"; 451 | COPY_PHASE_STRIP = NO; 452 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 453 | ENABLE_NS_ASSERTIONS = NO; 454 | ENABLE_STRICT_OBJC_MSGSEND = YES; 455 | GCC_C_LANGUAGE_STANDARD = gnu11; 456 | GCC_NO_COMMON_BLOCKS = YES; 457 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 458 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 459 | GCC_WARN_UNDECLARED_SELECTOR = YES; 460 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 461 | GCC_WARN_UNUSED_FUNCTION = YES; 462 | GCC_WARN_UNUSED_VARIABLE = YES; 463 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 464 | MTL_ENABLE_DEBUG_INFO = NO; 465 | MTL_FAST_MATH = YES; 466 | SDKROOT = iphoneos; 467 | SWIFT_COMPILATION_MODE = wholemodule; 468 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 469 | VALIDATE_PRODUCT = YES; 470 | }; 471 | name = Release; 472 | }; 473 | 8144D1A122140564008DBF5C /* Debug */ = { 474 | isa = XCBuildConfiguration; 475 | buildSettings = { 476 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 477 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 478 | CODE_SIGN_STYLE = Automatic; 479 | DEVELOPMENT_TEAM = 6L9EF3V856; 480 | INFOPLIST_FILE = Shades/Info.plist; 481 | LD_RUNPATH_SEARCH_PATHS = ( 482 | "$(inherited)", 483 | "@executable_path/Frameworks", 484 | ); 485 | PRODUCT_BUNDLE_IDENTIFIER = "com.zelazo.darth-shader"; 486 | PRODUCT_NAME = "$(TARGET_NAME)"; 487 | SWIFT_VERSION = 4.2; 488 | TARGETED_DEVICE_FAMILY = "1,2"; 489 | }; 490 | name = Debug; 491 | }; 492 | 8144D1A222140564008DBF5C /* Release */ = { 493 | isa = XCBuildConfiguration; 494 | buildSettings = { 495 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 496 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 497 | CODE_SIGN_STYLE = Automatic; 498 | DEVELOPMENT_TEAM = 6L9EF3V856; 499 | INFOPLIST_FILE = Shades/Info.plist; 500 | LD_RUNPATH_SEARCH_PATHS = ( 501 | "$(inherited)", 502 | "@executable_path/Frameworks", 503 | ); 504 | PRODUCT_BUNDLE_IDENTIFIER = "com.zelazo.darth-shader"; 505 | PRODUCT_NAME = "$(TARGET_NAME)"; 506 | SWIFT_VERSION = 4.2; 507 | TARGETED_DEVICE_FAMILY = "1,2"; 508 | }; 509 | name = Release; 510 | }; 511 | /* End XCBuildConfiguration section */ 512 | 513 | /* Begin XCConfigurationList section */ 514 | 8144D18922140562008DBF5C /* Build configuration list for PBXProject "Shades" */ = { 515 | isa = XCConfigurationList; 516 | buildConfigurations = ( 517 | 8144D19E22140564008DBF5C /* Debug */, 518 | 8144D19F22140564008DBF5C /* Release */, 519 | ); 520 | defaultConfigurationIsVisible = 0; 521 | defaultConfigurationName = Release; 522 | }; 523 | 8144D1A022140564008DBF5C /* Build configuration list for PBXNativeTarget "Shades" */ = { 524 | isa = XCConfigurationList; 525 | buildConfigurations = ( 526 | 8144D1A122140564008DBF5C /* Debug */, 527 | 8144D1A222140564008DBF5C /* Release */, 528 | ); 529 | defaultConfigurationIsVisible = 0; 530 | defaultConfigurationName = Release; 531 | }; 532 | /* End XCConfigurationList section */ 533 | }; 534 | rootObject = 8144D18622140562008DBF5C /* Project object */; 535 | } 536 | -------------------------------------------------------------------------------- /Shades.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Shades.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Shades.xcodeproj/xcuserdata/christopherzelazo.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Shades.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 2 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Shades/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Shades 4 | // 5 | // Created by Chris Zelazo on 2/12/19. 6 | // Copyright © 2019 Chris Zelazo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Shades/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Shades/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Shades/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 | -------------------------------------------------------------------------------- /Shades/Base.lproj/Main.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 | -------------------------------------------------------------------------------- /Shades/Code Table/Cells/CodeCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CodeCell.swift 3 | // Shades 4 | // 5 | // Created by Chris Zelazo on 2/18/19. 6 | // Copyright © 2019 Chris Zelazo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SavannaKit 11 | import SourceEditor 12 | 13 | public protocol CodeCellDelegate { 14 | func codeCellDidUpdateText(cell: CodeCell) 15 | func codeCell(cell: CodeCell, willBeginEditing textView: TextView) 16 | } 17 | 18 | public class CodeCell: UITableViewCell { 19 | 20 | public var delegate: CodeCellDelegate? 21 | 22 | lazy var lexer = SwiftLexer() 23 | 24 | public var code: String { 25 | get { 26 | return textView.text 27 | } 28 | set { 29 | textView.text = newValue 30 | } 31 | } 32 | 33 | public lazy var textView = SyntaxTextView() 34 | 35 | public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 36 | super.init(style: style, reuseIdentifier: reuseIdentifier) 37 | sharedInit() 38 | } 39 | 40 | required init?(coder aDecoder: NSCoder) { 41 | super.init(coder: aDecoder) 42 | sharedInit() 43 | } 44 | 45 | private func sharedInit() { 46 | backgroundColor = .clear 47 | 48 | textView.backgroundColor = .clear 49 | textView.contentTextView.isScrollEnabled = false 50 | textView.theme = DefaultSourceCodeTheme() 51 | textView.contentInset = UIEdgeInsets(top: 8.0, left: 0.0, bottom: 8.0, right: 0.0); 52 | textView.delegate = self 53 | 54 | addSubview(textView) 55 | textView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16.0).isActive = true 56 | textView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -10.0).isActive = true 57 | textView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16.0).isActive = true 58 | textView.topAnchor.constraint(equalTo: topAnchor, constant: 10.0).isActive = true 59 | textView.translatesAutoresizingMaskIntoConstraints = false 60 | } 61 | 62 | public override func prepareForReuse() { 63 | super.prepareForReuse() 64 | 65 | code = "" 66 | delegate = nil 67 | } 68 | 69 | } 70 | 71 | extension CodeCell: SyntaxTextViewDelegate { 72 | 73 | public func lexerForSource(_ source: String) -> Lexer { 74 | return lexer 75 | } 76 | 77 | public func didChangeText(_ syntaxTextView: SyntaxTextView) { 78 | delegate?.codeCellDidUpdateText(cell: self) 79 | } 80 | 81 | public func textViewWillBeginEditing(_ textView: TextView) { 82 | delegate?.codeCell(cell: self, willBeginEditing: textView) 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /Shades/Code Table/Cells/GeometryCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GeometryCell.swift 3 | // Shades 4 | // 5 | // Created by Chris Zelazo on 3/24/19. 6 | // Copyright © 2019 Chris Zelazo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol GeometryCellDelegate { 12 | func geometryControlDidSelect(geometry: Geometry) 13 | } 14 | 15 | final class GeometryCell: UITableViewCell { 16 | 17 | private let theme = DefaultSourceCodeTheme() 18 | 19 | public var delegate: GeometryCellDelegate? 20 | 21 | public lazy var control: UISegmentedControl = { 22 | let control = UISegmentedControl() 23 | control.apportionsSegmentWidthsByContent = true 24 | control.backgroundColor = .clear 25 | control.tintColor = .clear 26 | control.setTitleTextAttributes([ 27 | .font : theme.font.withSize(16.0), 28 | .foregroundColor: DefaultSourceCodeTheme.lineNumbersColor 29 | ], for: .normal) 30 | 31 | control.setTitleTextAttributes([ 32 | .font : theme.font.withSize(16.0), 33 | .foregroundColor: UIColor(red:0.98, green:0.71, blue:0.07, alpha:1.00) 34 | ], for: .selected) 35 | 36 | control.addTarget(self, action: #selector(handleControlSelection(_:)), for: .valueChanged) 37 | return control 38 | }() 39 | 40 | public var model: GeometryModel? { 41 | didSet { 42 | configure() 43 | } 44 | } 45 | 46 | required init?(coder aDecoder: NSCoder) { 47 | super.init(coder: aDecoder) 48 | sharedInit() 49 | } 50 | 51 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 52 | super.init(style: style, reuseIdentifier: reuseIdentifier) 53 | sharedInit() 54 | } 55 | 56 | private func sharedInit() { 57 | backgroundColor = theme.backgroundColor 58 | 59 | contentView.addSubview(control) 60 | NSLayoutConstraint.activate([ 61 | control.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), 62 | control.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), 63 | control.heightAnchor.constraint(equalToConstant: 16.0) 64 | ]) 65 | control.translatesAutoresizingMaskIntoConstraints = false 66 | } 67 | 68 | override func prepareForReuse() { 69 | super.prepareForReuse() 70 | 71 | delegate = nil 72 | } 73 | 74 | private func configure() { 75 | control.removeAllSegments() 76 | 77 | guard let model = model else { 78 | return 79 | } 80 | 81 | model.geometries.enumerated().forEach { (arg) in 82 | let (i, geometry) = arg 83 | control.insertSegment(withTitle: geometry.rawValue, at: i, animated: true) 84 | } 85 | 86 | if control.numberOfSegments > 0 { 87 | control.selectedSegmentIndex = 0 88 | } 89 | } 90 | 91 | @objc private func handleControlSelection(_ sender: UISegmentedControl) { 92 | let index = sender.selectedSegmentIndex 93 | guard let model = model, index < model.geometries.count else { 94 | assertionFailure("[Error] Unable to handle geometry selection") 95 | return 96 | } 97 | let geometry = model.geometries[index] 98 | delegate?.geometryControlDidSelect(geometry: geometry) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Shades/Code Table/Cells/SettingCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingCell.swift 3 | // Shades 4 | // 5 | // Created by Chris Zelazo on 4/7/19. 6 | // Copyright © 2019 Chris Zelazo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class SettingCell: UITableViewCell { 12 | 13 | private let theme = DefaultSourceCodeTheme() 14 | 15 | private lazy var control: UIButton = { 16 | let button = UIButton() 17 | button.setTitle("Record", for: .normal) 18 | button.addTarget(self, action: #selector(handleTap), for: .touchUpInside) 19 | return button 20 | }() 21 | 22 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 23 | super.init(style: style, reuseIdentifier: reuseIdentifier) 24 | sharedInit() 25 | } 26 | 27 | required init?(coder aDecoder: NSCoder) { 28 | super.init(coder: aDecoder) 29 | sharedInit() 30 | } 31 | 32 | private func sharedInit() { 33 | backgroundColor = theme.backgroundColor 34 | 35 | contentView.addSubview(control) 36 | NSLayoutConstraint.activate([ 37 | control.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), 38 | control.centerYAnchor.constraint(equalTo: contentView.centerYAnchor)]) 39 | control.translatesAutoresizingMaskIntoConstraints = false 40 | } 41 | 42 | @objc private func handleTap() { 43 | 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /Shades/Code Table/CodeTableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CodeTableView.swift 3 | // Shades 4 | // 5 | // Created by Chris Zelazo on 2/18/19. 6 | // Copyright © 2019 Chris Zelazo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ReplayKit 11 | 12 | public protocol CodeTableDelegate { 13 | func codeTable(_ table: CodeTableView, didSelect geometry: Geometry) 14 | func codeTable(_ table: CodeTableView, didUpdate shader: Shader) 15 | } 16 | 17 | open class CodeTableView: UITableViewController { 18 | 19 | /// String snippets of code that will be concatened, in-order 20 | /// to produce the final output shader. 21 | public var sections = [Section]() 22 | 23 | public var currentTextView: UITextView? 24 | 25 | /// Complete shader of snippet set. 26 | public var shader: Shader { 27 | return sections 28 | .items 29 | .compactMap { item in // [Shader] 30 | if case .code(let snippet) = item { 31 | return snippet.code 32 | } else { 33 | return nil 34 | } 35 | } 36 | .reduce("") { $0 + $1 } // Shader 37 | } 38 | 39 | private lazy var toolbar: InputToolbar = { 40 | let toolbar = InputToolbar() 41 | toolbar.delegate = self 42 | return toolbar 43 | }() 44 | 45 | public var delegate: CodeTableDelegate? 46 | 47 | required public init?(coder aDecoder: NSCoder) { 48 | super.init(coder: aDecoder) 49 | sharedInit() 50 | } 51 | 52 | public override init(style: UITableView.Style) { 53 | super.init(style: style) 54 | sharedInit() 55 | } 56 | 57 | private func sharedInit() { 58 | view.backgroundColor = .clear 59 | 60 | tableView.dataSource = self 61 | tableView.register(reusableCell: CodeCell.self) 62 | tableView.register(reusableCell: GeometryCell.self) 63 | tableView.register(reusableCell: SettingCell.self) 64 | tableView.register(reusableHeaderFooter: SectionHeaderView.self) 65 | tableView.keyboardDismissMode = .interactive 66 | tableView.allowsSelection = false 67 | tableView.separatorStyle = .none 68 | tableView.backgroundColor = .clear 69 | tableView.showsVerticalScrollIndicator = false 70 | } 71 | 72 | } 73 | 74 | // MARK: - UITableViewDataSource 75 | 76 | extension CodeTableView { 77 | 78 | open override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 79 | return sections[section].isCollapsed ? 0 : sections[section].items.count 80 | } 81 | 82 | open override func numberOfSections(in tableView: UITableView) -> Int { 83 | return sections.count 84 | } 85 | 86 | open override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 87 | let item = sections[indexPath.section].items[indexPath.row] 88 | 89 | switch item { 90 | case .code(let snippet): 91 | let cell = tableView.dequeue(reusableCell: CodeCell.self, indexPath: indexPath) 92 | cell.code = snippet.code 93 | cell.delegate = self 94 | return cell 95 | 96 | case .geometry(let geometry): 97 | let cell = tableView.dequeue(reusableCell: GeometryCell.self, indexPath: indexPath) 98 | cell.model = geometry 99 | cell.delegate = self 100 | return cell 101 | 102 | case .setting: 103 | let cell = tableView.dequeue(reusableCell: SettingCell.self, indexPath: indexPath) 104 | return cell 105 | 106 | } 107 | } 108 | 109 | open override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { 110 | let header = tableView.dequeue(reusableHeaderFooterCell: SectionHeaderView.self) 111 | header.titleLabel.text = sections[section].name 112 | header.section = section 113 | header.isCollapsed = sections[section].isCollapsed 114 | header.delegate = self 115 | return header 116 | } 117 | 118 | } 119 | 120 | extension CodeTableView: CodeCellDelegate { 121 | 122 | public func codeCellDidUpdateText(cell: CodeCell) { 123 | guard let indexPath = tableView.indexPath(for: cell) else { 124 | return 125 | } 126 | // This is less-than ideal 127 | sections.write(cell.textView.text, to: indexPath) 128 | delegate?.codeTable(self, didUpdate: shader) 129 | 130 | DispatchQueue.main.async { 131 | UIView.setAnimationsEnabled(false) 132 | self.tableView.beginUpdates() 133 | self.tableView.endUpdates() 134 | UIView.setAnimationsEnabled(true) 135 | } 136 | } 137 | 138 | public func codeCell(cell: CodeCell, willBeginEditing textView: UITextView) { 139 | currentTextView = textView 140 | textView.inputAccessoryView = toolbar 141 | } 142 | 143 | } 144 | 145 | extension CodeTableView: GeometryCellDelegate { 146 | 147 | func geometryControlDidSelect(geometry: Geometry) { 148 | delegate?.codeTable(self, didSelect: geometry) 149 | } 150 | 151 | } 152 | 153 | extension CodeTableView: ToolbarDelegate { 154 | 155 | public func toolbarKeyPressed(_ toolbar: InputToolbar, key: InputToolbarKey, value: String) { 156 | currentTextView?.insertText(value) 157 | } 158 | 159 | } 160 | 161 | extension CodeTableView: SectionHeaderViewDelegate { 162 | 163 | public func toggleSection(_ header: SectionHeaderView, section: Int) { 164 | TapticEngine.selection.feedback() 165 | 166 | let isCollapsed = !sections[section].isCollapsed 167 | 168 | // Toggle collapse 169 | header.isCollapsed = isCollapsed 170 | sections[section].isCollapsed = isCollapsed 171 | // header.setCollapsed(isCollapsed) 172 | 173 | // Reload the whole section 174 | tableView.beginUpdates() 175 | tableView.reloadSections(NSIndexSet(index: section) as IndexSet, with: .automatic) 176 | tableView.endUpdates() 177 | } 178 | 179 | } 180 | 181 | extension CodeTableView: RPPreviewViewControllerDelegate { 182 | 183 | public func previewControllerDidFinish(_ previewController: RPPreviewViewController) { 184 | previewController.dismiss(animated: true) 185 | } 186 | 187 | } 188 | -------------------------------------------------------------------------------- /Shades/Code Table/InputToolbar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InputToolbar.swift 3 | // Shades 4 | // 5 | // Created by Chris Zelazo on 3/1/19. 6 | // Copyright © 2019 Chris Zelazo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public protocol ToolbarDelegate { 12 | func toolbarKeyPressed(_ toolbar: InputToolbar, key: InputToolbarKey, value: String) 13 | } 14 | 15 | public class InputToolbar: UIInputView, UIInputViewAudioFeedback, InputToolbarKeyDelegate { 16 | 17 | public let keys: [String] = [".", ",", ";", "(", ")", "*", "/", "+", "-", "="] 18 | 19 | public var delegate: ToolbarDelegate? 20 | 21 | private lazy var stack: UIStackView = { 22 | let stack = UIStackView() 23 | stack.axis = .horizontal 24 | stack.spacing = 6.0 25 | stack.alignment = .fill 26 | stack.distribution = .fillEqually 27 | return stack 28 | }() 29 | 30 | convenience init() { 31 | self.init(frame: CGRect(x: 0, y: 0, width: 375, height: 53), inputViewStyle: .keyboard) 32 | } 33 | 34 | public override init(frame: CGRect, inputViewStyle: UIInputView.Style) { 35 | super.init(frame: frame, inputViewStyle: inputViewStyle) 36 | sharedInit() 37 | } 38 | 39 | public required init?(coder aDecoder: NSCoder) { 40 | super.init(coder: aDecoder) 41 | sharedInit() 42 | } 43 | 44 | private func sharedInit() { 45 | addSubview(stack) 46 | stack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 3.0).isActive = true 47 | stack.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -4.0).isActive = true 48 | stack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -3.0).isActive = true 49 | stack.topAnchor.constraint(equalTo: topAnchor, constant: 8.0).isActive = true 50 | stack.translatesAutoresizingMaskIntoConstraints = false 51 | 52 | let items: [UIButton] = keys.map { 53 | let key = InputToolbarKey(title: "\($0)", value: "\($0)") 54 | key.delegate = self 55 | key.addTarget(self, action: #selector(playInputClick), for: .touchDown) 56 | return key 57 | } 58 | 59 | allowsSelfSizing = true 60 | 61 | items.forEach { 62 | stack.addArrangedSubview($0) 63 | } 64 | } 65 | 66 | 67 | // MARK: - UIInputViewAudioFeedback 68 | 69 | public var enableInputClicksWhenVisible: Bool { 70 | return true 71 | } 72 | 73 | @objc private func playInputClick() { 74 | UIDevice.current.playInputClick() 75 | } 76 | 77 | 78 | // MARK: - InputToolbarKeyDelegate 79 | 80 | public func keyPressed(key: InputToolbarKey, value: String) { 81 | delegate?.toolbarKeyPressed(self, key: key, value: value) 82 | } 83 | } 84 | 85 | public protocol InputToolbarKeyDelegate { 86 | func keyPressed(key: InputToolbarKey, value: String) 87 | } 88 | 89 | public class InputToolbarKey: UIButton { 90 | 91 | private static let backgroundColorNormal = UIColor(white: 1.0, alpha: 0.3) 92 | private static let backgroundColorSelected = UIColor(red: 0.89, green: 0.81, blue: 0.89, alpha: 1.0) 93 | 94 | public var delegate: InputToolbarKeyDelegate? 95 | 96 | public private(set) var value: String = "" 97 | 98 | public var isShowingShadow: Bool { 99 | return false 100 | } 101 | 102 | convenience init(title: String?, value: String) { 103 | self.init(frame: .zero) 104 | self.setTitle(title, for: .normal) 105 | self.value = value 106 | } 107 | 108 | required init?(coder aDecoder: NSCoder) { 109 | super.init(coder: aDecoder) 110 | sharedInit() 111 | } 112 | 113 | override init(frame: CGRect) { 114 | super.init(frame: frame) 115 | sharedInit() 116 | } 117 | 118 | private func sharedInit() { 119 | backgroundColor = InputToolbarKey.backgroundColorNormal 120 | 121 | titleLabel?.font = UIFont.monospacedDigitSystemFont(ofSize: 22, weight: .regular) 122 | 123 | setTitleColor(.white, for: .normal) 124 | 125 | layer.cornerRadius = 5.0 126 | layer.masksToBounds = false 127 | contentEdgeInsets = UIEdgeInsets(top: 5, left: 0, bottom: 7, right: 0) 128 | 129 | addTarget(self, action: #selector(handleKeyPress), for: .touchUpInside) 130 | 131 | if isShowingShadow { 132 | layer.shadowColor = UIColor(white: 0.0, alpha: 1.0).cgColor 133 | layer.shadowOpacity = 0.35 134 | layer.shadowRadius = 0.0 135 | layer.shadowOffset = CGSize(width: 0, height: 2.0) 136 | } 137 | } 138 | 139 | public override func layoutSubviews() { 140 | super.layoutSubviews() 141 | 142 | if isShowingShadow { 143 | layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: 5.0).cgPath 144 | } 145 | } 146 | 147 | public override func touchesBegan(_ touches: Set, with event: UIEvent?) { 148 | super.touchesBegan(touches, with: event) 149 | setTitleColor(.black, for: .normal) 150 | backgroundColor = InputToolbarKey.backgroundColorSelected 151 | } 152 | 153 | public override func touchesEnded(_ touches: Set, with event: UIEvent?) { 154 | super.touchesEnded(touches, with: event) 155 | setTitleColor(.white, for: .normal) 156 | backgroundColor = InputToolbarKey.backgroundColorNormal 157 | } 158 | 159 | @objc private func handleKeyPress() { 160 | delegate?.keyPressed(key: self, value: value) 161 | } 162 | 163 | } 164 | -------------------------------------------------------------------------------- /Shades/Code Table/Section.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Section.swift 3 | // Shades 4 | // 5 | // Created by Chris Zelazo on 3/22/19. 6 | // Copyright © 2019 Chris Zelazo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SceneKit 11 | 12 | // MARK: - Section 13 | 14 | public struct Section { 15 | public var name: String 16 | public var items: [Item] 17 | public var isCollapsed: Bool 18 | 19 | init(_ name: String, items: [Item], isCollapsed: Bool = false) { 20 | self.name = name 21 | self.items = items 22 | self.isCollapsed = isCollapsed 23 | } 24 | } 25 | 26 | extension Array where Element == Section { 27 | 28 | var items: [Item] { 29 | return self.flatMap { $0.items } 30 | } 31 | 32 | @discardableResult 33 | public mutating func write(_ code: Shader, to indexPath: IndexPath) -> Bool { 34 | guard case .code(let snippet) = self[indexPath.section].items[indexPath.row] else { 35 | return false 36 | } 37 | self[indexPath.section].items[indexPath.row] = .code(Snippet(snippet.name, code: code, isFragmentShader: snippet.isFragmentShader)) 38 | return true 39 | } 40 | 41 | } 42 | 43 | // MARK: - Items 44 | 45 | /// Table wrapper item 46 | 47 | infix operator ~= 48 | 49 | public enum Item { 50 | case geometry(GeometryModel) 51 | case setting 52 | case code(Snippet) 53 | 54 | public var isGeometry: Bool { 55 | if case .geometry(_) = self { 56 | return true 57 | } else { 58 | return false 59 | } 60 | } 61 | 62 | public var isCode: Bool { 63 | if case .code(_) = self { 64 | return true 65 | } else { 66 | return false 67 | } 68 | } 69 | 70 | public static func ~=(lhs: Item, rhs: Item) -> Bool { 71 | switch (lhs, rhs) { 72 | case (.geometry(_), .geometry(_)): 73 | return true 74 | case (.code(_), .code(_)): 75 | return true 76 | default: 77 | return false 78 | } 79 | } 80 | } 81 | 82 | /// Code Snippet 83 | public struct Snippet { 84 | public var name: String 85 | public var code: Shader 86 | public var isFragmentShader: Bool 87 | 88 | init(_ name: String, code: String, isFragmentShader: Bool = false) { 89 | self.name = name 90 | self.code = code 91 | self.isFragmentShader = isFragmentShader 92 | } 93 | } 94 | 95 | /// Geometries Picker 96 | public struct GeometryModel { 97 | public let geometries = Geometry.allCases 98 | } 99 | 100 | public enum Geometry: String, CaseIterable { 101 | case plane = "Plane" 102 | case box = "Box" 103 | case pyramid = "Pyramid" 104 | case sphere = "Sphere" 105 | case torus = "Torus" 106 | 107 | var type: SCNGeometry.Type { 108 | switch self { 109 | case .plane: return SCNPlane.self 110 | case .box: return SCNBox.self 111 | case .pyramid: return SCNPyramid.self 112 | case .sphere: return SCNSphere.self 113 | case .torus: return SCNTorus.self 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Shades/Code Table/SectionHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SectionHeaderView.swift 3 | // Shades 4 | // 5 | // Created by Chris Zelazo on 2/24/19. 6 | // Copyright © 2019 Chris Zelazo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public protocol SectionHeaderViewDelegate { 12 | func toggleSection(_ header: SectionHeaderView, section: Int) 13 | } 14 | 15 | public class SectionHeaderView: UITableViewHeaderFooterView { 16 | 17 | public var section: Int = 0 18 | 19 | public var delegate: SectionHeaderViewDelegate? 20 | 21 | public var isCollapsed: Bool = false { 22 | didSet { 23 | configure() 24 | } 25 | } 26 | 27 | private lazy var tapRecognizer = UITapGestureRecognizer() 28 | 29 | public lazy var titleLabel = UILabel() 30 | private lazy var arrowLabel = UILabel() 31 | 32 | private lazy var containerView: UIStackView = { 33 | let stack = UIStackView() 34 | stack.axis = .horizontal 35 | stack.spacing = 12.0 36 | stack.alignment = .fill 37 | stack.distribution = .fill 38 | return stack 39 | }() 40 | 41 | public override var frame: CGRect { 42 | get { 43 | return super.frame 44 | } 45 | set { 46 | guard newValue.width != 0 && newValue.height != 0 else { 47 | return 48 | } 49 | super.frame = newValue 50 | } 51 | } 52 | 53 | override init(reuseIdentifier: String?) { 54 | super.init(reuseIdentifier: reuseIdentifier) 55 | 56 | // Setup 57 | tintColor = .clear 58 | backgroundView = .clear 59 | 60 | contentView.backgroundColor = UIColor(red: 31/255.0, green: 32/255, blue: 41/255, alpha: 0.6) 61 | 62 | contentView.addSubview(containerView) 63 | 64 | containerView.addArrangedSubview(titleLabel) 65 | titleLabel.textColor = .white 66 | titleLabel.font = UIFont(name: "Menlo-Bold", size: 18.0) 67 | 68 | containerView.addArrangedSubview(arrowLabel) 69 | arrowLabel.textColor = UIColor(red:0.98, green:0.71, blue:0.07, alpha:1.00) 70 | arrowLabel.font = UIFont(name: "Menlo", size: 16.0) 71 | 72 | // Layout 73 | let contentViewMargins = contentView.layoutMarginsGuide 74 | containerView.topAnchor.constraint(equalTo: contentViewMargins.topAnchor).isActive = true 75 | containerView.leadingAnchor.constraint(equalTo: contentViewMargins.leadingAnchor).isActive = true 76 | containerView.bottomAnchor.constraint(equalTo: contentViewMargins.bottomAnchor).isActive = true 77 | containerView.trailingAnchor.constraint(equalTo: contentViewMargins.trailingAnchor).isActive = true 78 | containerView.translatesAutoresizingMaskIntoConstraints = false 79 | 80 | configure() 81 | 82 | tapRecognizer.addTarget(self, action: #selector(handleTap(_:))) 83 | addGestureRecognizer(tapRecognizer) 84 | } 85 | 86 | required init?(coder aDecoder: NSCoder) { 87 | fatalError("init(coder:) has not been implemented") 88 | } 89 | 90 | private func configure() { 91 | // Arrow Label 92 | switch isCollapsed { 93 | case true: 94 | arrowLabel.text = "[+]" 95 | case false: 96 | arrowLabel.text = "[-]" 97 | } 98 | 99 | titleLabel.sizeToFit() 100 | arrowLabel.sizeToFit() 101 | } 102 | 103 | @objc private func handleTap(_ recognizer: UITapGestureRecognizer) { 104 | guard let header = recognizer.view as? SectionHeaderView else { 105 | return 106 | } 107 | delegate?.toggleSection(header, section: header.section) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Shades/Extensions/Bundle+Additions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bundle+Additions.swift 3 | // Shades 4 | // 5 | // Created by Chris Zelazo on 2/18/19. 6 | // Copyright © 2019 Chris Zelazo. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Bundle { 12 | 13 | public static func read(_ filepath: String, ofType: String?, bundle: Bundle = .main) -> String? { 14 | if let shaderPath = bundle.path(forResource: filepath, ofType: ofType) { 15 | do { 16 | return try String(contentsOfFile: shaderPath, encoding: .utf8) 17 | } catch { 18 | print("Bundle[\(bundle.debugDescription)] error reading from \(filepath).\(ofType ?? "") - \(error)") 19 | } 20 | } else { 21 | print("Bundle[\(bundle.debugDescription)] error locating \(filepath).\(ofType ?? "")") 22 | } 23 | return nil 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Shades/Extensions/Haptic.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Haptic.swift 3 | // Shades 4 | // 5 | // Created by Chris Zelazo on 3/13/19. 6 | // Copyright © 2019 Chris Zelazo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Generates iOS Device vibrations by UIFeedbackGenerator. 12 | open class TapticEngine { 13 | public static let impact: Impact = .init() 14 | public static let selection: Selection = .init() 15 | public static let notification: Notification = .init() 16 | 17 | 18 | /// Wrapper of `UIImpactFeedbackGenerator` 19 | open class Impact { 20 | /// Impact feedback styles 21 | /// 22 | /// - light: A impact feedback between small, light user interface elements. 23 | /// - medium: A impact feedback between moderately sized user interface elements. 24 | /// - heavy: A impact feedback between large, heavy user interface elements. 25 | public enum ImpactStyle { 26 | case light, medium, heavy 27 | } 28 | 29 | private var style: ImpactStyle = .light 30 | private var generator: Any? = Impact.makeGenerator(.light) 31 | 32 | private static func makeGenerator(_ style: ImpactStyle) -> Any? { 33 | guard #available(iOS 10.0, *) else { return nil } 34 | 35 | let feedbackStyle: UIImpactFeedbackGenerator.FeedbackStyle 36 | switch style { 37 | case .light: 38 | feedbackStyle = .light 39 | case .medium: 40 | feedbackStyle = .medium 41 | case .heavy: 42 | feedbackStyle = .heavy 43 | } 44 | let generator: UIImpactFeedbackGenerator = UIImpactFeedbackGenerator(style: feedbackStyle) 45 | generator.prepare() 46 | return generator 47 | } 48 | 49 | private func updateGeneratorIfNeeded(_ style: ImpactStyle) { 50 | guard self.style != style else { return } 51 | 52 | generator = Impact.makeGenerator(style) 53 | self.style = style 54 | } 55 | 56 | public func feedback(_ style: ImpactStyle) { 57 | guard #available(iOS 10.0, *) else { return } 58 | 59 | updateGeneratorIfNeeded(style) 60 | 61 | guard let generator = generator as? UIImpactFeedbackGenerator else { return } 62 | 63 | generator.impactOccurred() 64 | generator.prepare() 65 | } 66 | 67 | public func prepare(_ style: ImpactStyle) { 68 | guard #available(iOS 10.0, *) else { return } 69 | 70 | updateGeneratorIfNeeded(style) 71 | 72 | guard let generator = generator as? UIImpactFeedbackGenerator else { return } 73 | 74 | generator.prepare() 75 | } 76 | } 77 | 78 | 79 | /// Wrapper of `UISelectionFeedbackGenerator` 80 | open class Selection { 81 | private var generator: Any? = { 82 | guard #available(iOS 10.0, *) else { return nil } 83 | 84 | let generator: UISelectionFeedbackGenerator = UISelectionFeedbackGenerator() 85 | generator.prepare() 86 | return generator 87 | }() 88 | 89 | public func feedback() { 90 | guard #available(iOS 10.0, *), 91 | let generator = generator as? UISelectionFeedbackGenerator else { return } 92 | 93 | generator.selectionChanged() 94 | generator.prepare() 95 | } 96 | 97 | public func prepare() { 98 | guard #available(iOS 10.0, *), 99 | let generator = generator as? UISelectionFeedbackGenerator else { return } 100 | 101 | generator.prepare() 102 | } 103 | } 104 | 105 | 106 | /// Wrapper of `UINotificationFeedbackGenerator` 107 | open class Notification { 108 | /// Notification feedback types 109 | /// 110 | /// - success: A notification feedback, indicating that a task has completed successfully. 111 | /// - warning: A notification feedback, indicating that a task has produced a warning. 112 | /// - error: A notification feedback, indicating that a task has failed. 113 | public enum NotificationType { 114 | case success, warning, error 115 | } 116 | 117 | private var generator: Any? = { 118 | guard #available(iOS 10.0, *) else { return nil } 119 | 120 | let generator: UINotificationFeedbackGenerator = UINotificationFeedbackGenerator() 121 | generator.prepare() 122 | return generator 123 | }() 124 | 125 | public func feedback(_ type: NotificationType) { 126 | guard #available(iOS 10.0, *), 127 | let generator = generator as? UINotificationFeedbackGenerator else { return } 128 | 129 | let feedbackType: UINotificationFeedbackGenerator.FeedbackType 130 | switch type { 131 | case .success: 132 | feedbackType = .success 133 | case .warning: 134 | feedbackType = .warning 135 | case .error: 136 | feedbackType = .error 137 | } 138 | generator.notificationOccurred(feedbackType) 139 | generator.prepare() 140 | } 141 | 142 | public func prepare() { 143 | guard #available(iOS 10.0, *), 144 | let generator = generator as? UINotificationFeedbackGenerator else { return } 145 | 146 | generator.prepare() 147 | } 148 | } 149 | } 150 | 151 | -------------------------------------------------------------------------------- /Shades/Extensions/Math+Additions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Math+Additions.swift 3 | // Shades 4 | // 5 | // Created by Chris Zelazo on 3/5/19. 6 | // Copyright © 2019 Chris Zelazo. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension FloatingPoint { 12 | 13 | // Interpolates `value` between `bounds` mapping an output between `outputBounds`. 14 | public static func lerp(value: Self, between bounds: (Self, Self), outputBounds: (Self, Self)) -> Self { 15 | return value.lerp(bounds: bounds, outputBounds: outputBounds) 16 | } 17 | 18 | // Interpolates a value `self` between `bounds` mapping an output between `outputBounds`. 19 | public func lerp(bounds: (Self, Self), outputBounds: (Self, Self)) -> Self { 20 | return outputBounds.0 + ((self - bounds.0) / (bounds.1 - bounds.0) * (outputBounds.1 - outputBounds.0)) 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Shades/Extensions/UIKit+Additions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIKit+Additions.swift 3 | // Shades 4 | // 5 | // Created by Chris Zelazo on 3/5/19. 6 | // Copyright © 2019 Chris Zelazo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import UIKit.UIGestureRecognizerSubclass 11 | 12 | public extension UIApplication { 13 | 14 | static var statusBarSize: CGSize { 15 | return UIApplication.shared.statusBarFrame.size 16 | } 17 | 18 | } 19 | 20 | public extension UITouch { 21 | 22 | /// UITouch force value normalized to [0, 1] 23 | var normalizedForce: CGFloat { 24 | return force / maximumPossibleForce 25 | } 26 | 27 | } 28 | 29 | //Since 3D Touch isn't available before iOS 9, we can use the availability APIs to ensure no one uses this class for earlier versions of the OS. 30 | @available(iOS 9.0, *) 31 | public class ForceTouchGestureRecognizer: UIGestureRecognizer { 32 | 33 | /// Force value normalized from [0.0, 1.0], representing no force to max force. 34 | public private(set) var force: CGFloat = 0.0 35 | public var maximumForce: CGFloat = 4.0 36 | 37 | /// Minimum normalized force value to trigger recognizer and move to `began` state. 38 | public var minimumForceActivation: CGFloat = 0.1 39 | 40 | convenience init() { 41 | self.init(target: nil, action: nil) 42 | } 43 | 44 | //We override the initializer because UIGestureRecognizer's cancelsTouchesInView property is true by default. If you were to, say, add this recognizer to a tableView's cell, it would prevent didSelectRowAtIndexPath from getting called. Thanks for finding this bug, Jordan Hipwell! 45 | public override init(target: Any?, action: Selector?) { 46 | super.init(target: target, action: action) 47 | cancelsTouchesInView = false 48 | } 49 | 50 | public override func touchesBegan(_ touches: Set, with event: UIEvent) { 51 | super.touchesBegan(touches, with: event) 52 | normalizeForce(touches) 53 | 54 | guard touches.force > minimumForceActivation else { 55 | return 56 | } 57 | state = .began 58 | } 59 | 60 | public override func touchesMoved(_ touches: Set, with event: UIEvent) { 61 | super.touchesMoved(touches, with: event) 62 | normalizeForce(touches) 63 | 64 | let validStates: [UIGestureRecognizer.State] = [.began, .changed] 65 | if validStates.contains(state) { 66 | // Continue handling events 67 | state = .changed 68 | } else if touches.force > minimumForceActivation { 69 | // Begin gesture recognizing, move to `began`. 70 | state = .began 71 | } 72 | } 73 | 74 | public override func touchesEnded(_ touches: Set, with event: UIEvent) { 75 | super.touchesEnded(touches, with: event) 76 | normalizeForce(touches) 77 | 78 | let validStates: [UIGestureRecognizer.State] = [.began, .changed] 79 | guard validStates.contains(state) else { 80 | return 81 | } 82 | state = .ended 83 | } 84 | 85 | public override func touchesCancelled(_ touches: Set, with event: UIEvent) { 86 | super.touchesCancelled(touches, with: event) 87 | normalizeForce(touches) 88 | 89 | guard state != .possible else { 90 | return 91 | } 92 | state = .cancelled 93 | } 94 | 95 | private func normalizeForce(_ touches: Set) { 96 | //Putting a guard statement here to make sure we don't fire off our target's selector event if a touch doesn't exist to begin with. 97 | guard let firstTouch = touches.first else { return } 98 | 99 | //Just in case the developer set a maximumForce that is higher than the touch's maximumPossibleForce, I'm setting the maximumForce to the lower of the two values. 100 | maximumForce = min(firstTouch.maximumPossibleForce, maximumForce) 101 | 102 | //Now that I have a proper maximumForce, I'm going to use that and normalize it so the developer can use a value between 0.0 and 1.0. 103 | force = firstTouch.force / maximumForce 104 | } 105 | 106 | //This function is called automatically by UIGestureRecognizer when our state is set to .Ended. We want to use this function to reset our internal state. 107 | public override func reset() { 108 | super.reset() 109 | force = 0.0 110 | } 111 | } 112 | 113 | fileprivate extension Set where Element == UITouch { 114 | 115 | /// Normalized force from [0, 1] 116 | var force: CGFloat { 117 | guard let touch = self.first else { 118 | return 0.0 119 | } 120 | return touch.force / touch.maximumPossibleForce 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /Shades/Extensions/UITableView+Additions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITableView+Additions.swift 3 | // Shades 4 | // 5 | // Created by Chris Zelazo on 2/18/19. 6 | // Copyright © 2019 Chris Zelazo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension UITableView { 12 | 13 | // Registers a given class as a reusable cell using the standard reuse 14 | // naming convention, e.g. `Redux.CommentCell`. 15 | public func register(reusableCell type: T.Type) { 16 | register(T.self, forCellReuseIdentifier: String(describing: type)) 17 | } 18 | 19 | // Registers a given class as a reusable header/footer cell using the standard reuse 20 | // naming convention, e.g. `Redux.CommentCell`. 21 | public func register(reusableHeaderFooter type: T.Type) { 22 | register(T.self, forHeaderFooterViewReuseIdentifier: String(describing: type)) 23 | } 24 | 25 | // Dequeues a cell of the specified type at an index path. 26 | // Note: Uses name of class as identifier, e.g. `Redux.CommentCell`. Ensure 27 | // the cell's Storyboard identifier matches this convention. 28 | public func dequeue(reusableCell type: T.Type, indexPath: IndexPath) -> T { 29 | guard let cell = dequeueReusableCell(withIdentifier: String(describing: type), for: indexPath) as? T else { 30 | fatalError("Couldn't find UITableViewCell for \(String(describing: type))") 31 | } 32 | return cell 33 | } 34 | 35 | // Dequeues a cell of the specified type at an index path. 36 | // Note: Uses name of class as identifier, e.g. `Redux.CommentCell`. Ensure 37 | // the cell's Storyboard identifier matches this convention. 38 | public func dequeue(reusableHeaderFooterCell type: T.Type) -> T { 39 | guard let headerFooter = dequeueReusableHeaderFooterView(withIdentifier: String(describing: type)) as? T else { 40 | fatalError("Couldn't find UITableViewHeaderFooterView for \(String(describing: type))") 41 | } 42 | return headerFooter 43 | } 44 | 45 | public func reloadData(_ completion: @escaping () -> Void) { 46 | UIView.animate(withDuration: 0, animations: { 47 | self.reloadData() 48 | }, completion: { _ in 49 | completion() 50 | }) 51 | } 52 | 53 | public enum Direction { 54 | case top 55 | case bottom 56 | } 57 | 58 | public func scroll(to: UITableView.Direction, animated: Bool = true) { 59 | let yOffset: CGFloat 60 | switch to { 61 | case .top: 62 | yOffset = 0 63 | case .bottom: 64 | yOffset = contentSize.height - bounds.size.height 65 | } 66 | setContentOffset(CGPoint(x: 0, y: yOffset), animated: animated) 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /Shades/Extensions/UIView+Additions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Additions.swift 3 | // Shades 4 | // 5 | // Created by Chris Zelazo on 2/25/19. 6 | // Copyright © 2019 Chris Zelazo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension UIView { 12 | 13 | /// A `.zero` frame view with a clear `backgroundColor`. 14 | static var clear: UIView = UIView.color(.clear) 15 | 16 | static func color(_ color: UIColor) -> UIView { 17 | let view = UIView() 18 | view.backgroundColor = color 19 | return view 20 | } 21 | 22 | // MARK: - Blurs 23 | 24 | static var blur: UIVisualEffectView = UIView.blurView(style: .regular) 25 | 26 | static var prominentBlur: UIVisualEffectView = UIView.blurView(style: .prominent) 27 | 28 | static var lightBlur: UIVisualEffectView = UIView.blurView(style: .light) 29 | 30 | static var darkBlur: UIVisualEffectView = UIView.blurView(style: .dark) 31 | 32 | private static func blurView(style: UIBlurEffect.Style) -> UIVisualEffectView { 33 | let view = UIVisualEffectView(effect: UIBlurEffect(style: style)) 34 | view.clipsToBounds = true 35 | return view 36 | } 37 | 38 | } 39 | 40 | public extension UISpringTimingParameters { 41 | 42 | /// A design-friendly way to create a spring timing curve. 43 | /// 44 | /// - Parameters: 45 | /// - damping: The 'bounciness' of the animation. Value must be between 0 and 1. 46 | /// - response: The 'speed' of the animation. 47 | /// - initialVelocity: The vector describing the starting motion of the property. Optional, default is `.zero`. 48 | convenience init(damping: CGFloat, response: CGFloat, initialVelocity: CGVector = .zero) { 49 | let stiffness = pow(2 * .pi / response, 2) 50 | let damp = 4 * .pi * damping / response 51 | self.init(mass: 1, stiffness: stiffness, damping: damp, initialVelocity: initialVelocity) 52 | } 53 | 54 | } 55 | 56 | public extension UIViewPropertyAnimator { 57 | 58 | /// A design-friendly way to create a spring timing curve. Note `duration` 59 | /// is automatically calculated. 60 | /// 61 | /// - Parameters: 62 | /// - damping: The 'bounciness' of the animation. Value must be between 0 and 1. 63 | /// - response: The 'speed' of the animation. 64 | /// - initialVelocity: The vector describing the starting motion of the property. Optional, default is `.zero`. 65 | /// - animations: Animation block that will be passed directly into the newly created UIViewPropertyAnimator 66 | convenience init(damping: CGFloat, response: CGFloat, initialVelocity: CGVector = .zero, animations: @escaping () -> Void) { 67 | let timingParameters = UISpringTimingParameters(damping: damping, response: response, initialVelocity: initialVelocity) 68 | let duration = UIViewPropertyAnimator(duration: 0, timingParameters: timingParameters).duration 69 | self.init(duration: duration, timingParameters: timingParameters) 70 | self.addAnimations(animations) 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /Shades/GLSLLexer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GLSLLexer.swift 3 | // Shades 4 | // 5 | // Created by Chris Zelazo on 2/19/19. 6 | // Copyright © 2019 Chris Zelazo. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SavannaKit 11 | import SourceEditor 12 | 13 | public class GLSLLexer: SourceCodeRegexLexer { 14 | 15 | public init() { 16 | 17 | } 18 | 19 | lazy var generators: [TokenGenerator] = { 20 | 21 | var generators = [TokenGenerator?]() 22 | 23 | // UI/App Kit 24 | generators.append(regexGenerator("\\b(gl_)[A-Z][a-zA-Z]+\\b", tokenType: .identifier)) 25 | 26 | // Functions 27 | 28 | generators.append(regexGenerator("\\b.(?=\\()", tokenType: .identifier)) 29 | 30 | 31 | generators.append(regexGenerator("(?<=(\\s|\\[|,|:))(\\d|\\.|_)+", tokenType: .number)) 32 | 33 | generators.append(regexGenerator("\\.[A-Za-z_]+\\w*", tokenType: .identifier)) 34 | 35 | let keywords = "attribute centroid sample patch const flat in inout invariant noperspective out smooth uniform varying buffer shared lowp mediump highp break case continue default discard do else for if return switch while".components(separatedBy: " ") 36 | 37 | generators.append(keywordGenerator(keywords, tokenType: .keyword)) 38 | 39 | let stdlibIdentifiers = "abs acos all any asin atan ceil clamp cos cross degrees dFdx dFdy distance dot equal exp exp2 faceforward floor fract ftransform fwidth greaterThan greaterThanEqual inversesqrt length lessThan lessThanEqual log log2 matrixCompMult max min mix mod noise1 noise2 noise3 noise4 normalize not notEqual outerProduct pow radians reflect refract shadow1D shadow1DLod shadow1DProj shadow1DProjLod shadow2D shadow2DLod shadow2DProj shadow2DProjLod sign sin smoothstep sqrt step tan texture1D texture1DLod texture1DProj texture1DProjLod texture2D texture2DLod texture2DProj texture2DProjLod texture3D texture3DLod texture3DProj texture3DProjLod textureCube textureCubeLod transpose void bool int uint float double vec2 vec3 vec4 dvec2 dvec3 dvec4 bvec2 bvec3 bvec4 ivec2 ivec3 ivec4 uvec2 uvec3 uvec4 mat2 mat3 mat4 mat2x2 mat3x2 mat4x2 mat2x3 mat3x3 mat4x3 mat2x4 mat3x4 mat4x4 dmat2 dmat3 dmat4 dmat2x2 dmat3x2 dmat4x2 dmat2x3 dmat3x3 dmat4x3 dmat2x4 dmat3x4 dmat4x4 sampler1 sampler2 sampler3 samplerCube sampler2DRect sampler2DRectShadow samplerBuffer sampler2DMS sampler2DMSArray struct isamplerCube isampler2DRect isamplerBuffer isampler2DMS isampler2DMSArray usamplerCube usampler2DRect usamplerBuffer usampler2DMS usampler2DMSArray".components(separatedBy: " ") 40 | 41 | generators.append(keywordGenerator(stdlibIdentifiers, tokenType: .identifier)) 42 | 43 | // Line comment 44 | generators.append(regexGenerator("//(.*)", tokenType: .comment)) 45 | 46 | // Block comment 47 | generators.append(regexGenerator("(/\\*)(.*)(\\*/)", options: [.dotMatchesLineSeparators], tokenType: .comment)) 48 | 49 | // Single-line string literal 50 | generators.append(regexGenerator("(\"|@\")[^\"\\n]*(@\"|\")", tokenType: .string)) 51 | 52 | // Multi-line string literal 53 | generators.append(regexGenerator("(\"\"\")(.*?)(\"\"\")", options: [.dotMatchesLineSeparators], tokenType: .string)) 54 | 55 | // Editor placeholder 56 | var editorPlaceholderPattern = "(<#)[^\"\\n]*" 57 | editorPlaceholderPattern += "(#>)" 58 | generators.append(regexGenerator(editorPlaceholderPattern, tokenType: .editorPlaceholder)) 59 | 60 | return generators.compactMap( { $0 }) 61 | }() 62 | 63 | public func generators(source: String) -> [TokenGenerator] { 64 | return generators 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /Shades/GLSLTheme.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GLSLTheme.swift 3 | // Shades 4 | // 5 | // Created by Chris Zelazo on 2/19/19. 6 | // Copyright © 2019 Chris Zelazo. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SavannaKit 11 | import SourceEditor 12 | 13 | public struct DefaultSourceCodeTheme: SourceCodeTheme { 14 | 15 | public init() { 16 | 17 | } 18 | 19 | public static var lineNumbersColor: Color { 20 | return Color(red: 100/255, green: 100/255, blue: 100/255, alpha: 1.0) 21 | } 22 | 23 | public let lineNumbersStyle: LineNumbersStyle? = LineNumbersStyle(font: Font(name: "Menlo", size: 16)!, textColor: lineNumbersColor) 24 | 25 | public let gutterStyle: GutterStyle = GutterStyle(backgroundColor: Color(red: 21/255.0, green: 22/255, blue: 31/255, alpha: 1.0), minimumWidth: 32) 26 | 27 | public let font = Font(name: "Menlo", size: 15)! 28 | public let fontBold = Font(name: "Menlo-Bold", size: 15)! 29 | 30 | public let caretColor: Color = Color(red:0.98, green:0.71, blue:0.07, alpha:1.00) 31 | 32 | public let backgroundColor = Color(red: 31/255.0, green: 32/255, blue: 41/255, alpha: 0.3) 33 | 34 | public func color(for syntaxColorType: SourceCodeTokenType) -> Color { 35 | 36 | switch syntaxColorType { 37 | case .plain: 38 | return .white 39 | 40 | case .number: 41 | return Color(red: 116/255, green: 109/255, blue: 176/255, alpha: 1.0) 42 | 43 | case .string: 44 | return Color(red: 211/255, green: 35/255, blue: 46/255, alpha: 1.0) 45 | 46 | case .identifier: 47 | return Color(red: 20/255, green: 156/255, blue: 146/255, alpha: 1.0) 48 | 49 | case .keyword: 50 | return Color(red: 215/255, green: 0, blue: 143/255, alpha: 1.0) 51 | 52 | case .comment: 53 | return Color(red: 69.0/255.0, green: 187.0/255.0, blue: 62.0/255.0, alpha: 1.0) 54 | 55 | case .editorPlaceholder: 56 | return backgroundColor 57 | } 58 | 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /Shades/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPhotoLibraryUsageDescription 6 | Allow access to save screen recordings to your camera roll. 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleDisplayName 10 | Darth Shader 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | $(PRODUCT_NAME) 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | 1.0 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Shades/Recording/Photos+Additions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Photos+Additions.swift 3 | // Shades 4 | // 5 | // Created by Chris Zelazo on 4/7/19. 6 | // Copyright © 2019 Chris Zelazo. All rights reserved. 7 | // 8 | 9 | import Photos 10 | 11 | public extension PHPhotoLibrary { 12 | 13 | /// Returns information about your app’s authorization to access 14 | /// the user’s photo library. 15 | static var isAuthorized: Bool { 16 | return PHPhotoLibrary.authorizationStatus() == .authorized 17 | } 18 | 19 | /// Returns true when the user has not denied access or yet been prompted. 20 | static var canAuthorize: Bool { 21 | return PHPhotoLibrary.authorizationStatus() == .notDetermined 22 | } 23 | 24 | } 25 | 26 | -------------------------------------------------------------------------------- /Shades/Recording/Recorder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Recorder.swift 3 | // Shades 4 | // 5 | // Created by Chris Zelazo on 4/7/19. 6 | // Copyright © 2019 Chris Zelazo. All rights reserved. 7 | // 8 | 9 | import ReplayKit 10 | 11 | public protocol RecorderDelegate { 12 | 13 | } 14 | 15 | public final class Recorder: RPScreenRecorder { 16 | 17 | public static let sharedIndicatorWindow = RecordWindow() 18 | 19 | /// A Boolean value that indicates whether the app is currently recording. 20 | public class var isRecording: Bool { 21 | return shared().isRecording 22 | } 23 | 24 | /// Starts app recording with a completion handler. Note that before recording 25 | /// actually starts, the user may be prompted with UI to confirm recording. 26 | public class func startRecording(handler: ((Error?) -> Void)? = nil) { 27 | // Hacky use of RPScreenRecorder.shared as delegate to stop recording. 28 | sharedIndicatorWindow.show(delegate: shared()) 29 | 30 | shared().startRecording { error in 31 | if let error = error { 32 | print("[Recorder] Failed to start screen recording. \(error)") 33 | } 34 | handler?(error) 35 | } 36 | } 37 | 38 | /// Stops app recording with a completion handler. 39 | public class func stopRecording(handler: ((RPPreviewViewController?, Error?) -> Void)? = nil) { 40 | sharedIndicatorWindow.hide() 41 | 42 | shared().stopRecording { previewController, error in 43 | if let error = error { 44 | print("[Recorder] Failed to stop screen recording. \(error)") 45 | } 46 | handler?(previewController, error) 47 | } 48 | } 49 | } 50 | 51 | extension RPScreenRecorder: RecordWindowDelegate { 52 | 53 | // Hacky use of RPScreenRecorder.shared as delegate to stop recording. 54 | public func didRequestStopRecording() { 55 | Recorder.stopRecording() 56 | } 57 | 58 | } 59 | 60 | public protocol RecordWindowDelegate { 61 | func didRequestStopRecording() 62 | } 63 | 64 | public class RecordWindow: UIWindow { 65 | 66 | public var delegate: RecordWindowDelegate? 67 | 68 | private lazy var button: UIButton = { 69 | let button = UIButton() 70 | button.backgroundColor = UIColor.red.withAlphaComponent(0.85) 71 | button.addTarget(self, action: #selector(handleStopRecording), for: .touchUpInside) 72 | return button 73 | }() 74 | 75 | convenience init() { 76 | self.init(frame: CGRect(origin: .zero, size: CGSize(width: 375, height: 40))) 77 | 78 | addSubview(button) 79 | NSLayoutConstraint.activate([ 80 | button.leadingAnchor.constraint(equalTo: leadingAnchor), 81 | button.bottomAnchor.constraint(equalTo: bottomAnchor), 82 | button.trailingAnchor.constraint(equalTo: trailingAnchor), 83 | button.topAnchor.constraint(equalTo: topAnchor)]) 84 | button.translatesAutoresizingMaskIntoConstraints = false 85 | } 86 | 87 | public func show(delegate: RecordWindowDelegate) { 88 | self.delegate = delegate 89 | 90 | windowLevel = .alert 91 | makeKeyAndVisible() 92 | 93 | setWindowHidden(false) 94 | } 95 | 96 | public func hide() { 97 | delegate = nil 98 | setWindowHidden(true) 99 | } 100 | 101 | private func setWindowHidden(_ hidden: Bool) { 102 | let transform: CGAffineTransform = hidden ? CGAffineTransform(translationX: 0, y: -bounds.height) : .identity 103 | let initialVelocity: CGVector = .zero//hidden ? .zero : CGVector(dx: 0, dy: 15.0) 104 | let animator = UIViewPropertyAnimator(damping: 0.95, response: 0.25, initialVelocity: initialVelocity) { 105 | self.transform = transform 106 | } 107 | animator.addCompletion { _ in 108 | self.isHidden = hidden 109 | } 110 | animator.startAnimation() 111 | } 112 | 113 | @objc private func handleStopRecording() { 114 | delegate?.didRequestStopRecording() 115 | } 116 | 117 | deinit { 118 | delegate = nil 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /Shades/SceneController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneController.swift 3 | // Shades 4 | // 5 | // Created by Chris Zelazo on 2/12/19. 6 | // Copyright © 2019 Chris Zelazo. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SceneKit 11 | 12 | open class SceneController: SCNScene { 13 | 14 | public let node = SCNNode() 15 | 16 | weak public private(set) var view: SCNView? 17 | 18 | public var geometryType: SCNGeometry.Type? { 19 | didSet { 20 | guard let type = geometryType, let fragment = fragment else { 21 | return 22 | } 23 | node.geometry = geometry(of: type) 24 | updateFragmentShaderModifier(fragment) 25 | } 26 | } 27 | 28 | public var size: CGSize = .zero { 29 | didSet { 30 | // switch node.geometry { 31 | // case let g as SCNPlane: 32 | // g.width = size.width 33 | // g.height = size.height 34 | // 35 | // default: 36 | // assertionFailure("Unexpected geometry: \(node.geometry.debugDescription)") 37 | // } 38 | } 39 | } 40 | 41 | private var visibleSize: CGSize { 42 | guard let view = view else { 43 | return .zero 44 | } 45 | 46 | let aspectRatio: CGFloat = view.bounds.width / view.bounds.height 47 | let height: CGFloat = 2 * tan(fieldOfView / 2.0) * CGFloat(cameraNode.position.z) 48 | return CGSize(width: height * aspectRatio, height: height) 49 | } 50 | 51 | public private(set) lazy var cameraNode: SCNNode = { 52 | let node = SCNNode() 53 | 54 | let camera = SCNCamera() 55 | node.camera = camera 56 | 57 | return node 58 | }() 59 | 60 | /// The vertical or horizontal viewing angle of the camera. 61 | public var fieldOfView: CGFloat { 62 | guard let camera = cameraNode.camera else { 63 | return 0.0 64 | } 65 | return camera.fieldOfView * .pi / 180.0 66 | } 67 | 68 | /// The fragment shader for `SCNNode.SCNGeometry.firstMaterial`. 69 | public var fragment: Shader? { 70 | didSet { 71 | guard let fragment = fragment else { 72 | return 73 | } 74 | updateFragmentShaderModifier(fragment) 75 | } 76 | } 77 | 78 | convenience init(view: SCNView) { 79 | self.init() 80 | 81 | // Retain a weak reference of the view so we can swap geometries. 82 | // This is not great. 83 | self.view = view 84 | 85 | /// Node 86 | rootNode.addChildNode(node) 87 | 88 | /// Camera 89 | cameraNode.position = SCNVector3(x: 0, y: 0, z: 10) 90 | rootNode.addChildNode(cameraNode) 91 | 92 | /// Node Geometry 93 | size = visibleSize 94 | node.geometry = geometry(of: SCNPlane.self) 95 | 96 | /// View 97 | view.allowsCameraControl = true 98 | 99 | // Attatch self (SCNNode) to view 100 | view.scene = self 101 | } 102 | 103 | deinit { 104 | self.view = nil 105 | } 106 | 107 | // MARK: - Utility 108 | 109 | private func updateFragmentShaderModifier(_ shader: Shader) { 110 | node.geometry?.firstMaterial?.shaderModifiers = [.fragment: shader] 111 | } 112 | 113 | private func geometry(of type: T.Type) -> T { 114 | let minDimension = min(size.width, size.height) 115 | 116 | let g: SCNGeometry 117 | switch type { 118 | case is SCNPlane.Type: 119 | g = SCNPlane(width: size.width, height: size.height) 120 | 121 | case is SCNBox.Type: 122 | let dimension = minDimension * 0.8 123 | g = SCNBox(width: dimension, height: dimension, length: dimension, chamferRadius: 0) 124 | 125 | case is SCNPyramid.Type: 126 | let dimension = minDimension * 0.8 127 | let height = 0.5 * sqrt(2) * dimension 128 | g = SCNPyramid(width: dimension, height: height, length: dimension) 129 | 130 | case is SCNSphere.Type: 131 | let radius = minDimension * 0.8 / 2.0 132 | g = SCNSphere(radius: radius) 133 | 134 | case is SCNTorus.Type: 135 | let ringRadius = minDimension * 0.8 / 2.0 136 | let pipeRadius = ringRadius / 8.0 137 | g = SCNTorus(ringRadius: ringRadius, pipeRadius: pipeRadius) 138 | 139 | default: 140 | g = geometry(of: SCNPlane.self) 141 | } 142 | 143 | g.firstMaterial?.isDoubleSided = true 144 | return g as! T 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /Shades/SceneViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneViewController.swift 3 | // Shades 4 | // 5 | // Created by Chris Zelazo on 2/18/19. 6 | // Copyright © 2019 Chris Zelazo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SceneKit 11 | import ReplayKit 12 | 13 | /// SceneViewController 14 | /// 15 | /// UIViewController subclass with a SCNView 16 | open class SceneViewController: UIViewController { 17 | 18 | public var codeTable: CodeTableView? 19 | 20 | private var beganForceTouch: Bool = false 21 | 22 | open override var prefersStatusBarHidden: Bool { 23 | return true 24 | } 25 | 26 | /// The SCNView for this view controller. Added to the top of the underlying 27 | /// UIViewController's subview stack when set. 28 | public var sceneView: SCNView? { 29 | didSet { 30 | guard let sceneView = sceneView, !view.subviews.contains(sceneView) else { 31 | return 32 | } 33 | sceneController = SceneController(view: sceneView) 34 | view.addSubview(sceneView) 35 | } 36 | } 37 | 38 | /// `SCNNode`, `SCNGeometry` and shaders which control the local variable `sceneView`. 39 | public private(set) var sceneController: SceneController? 40 | 41 | private lazy var tapRecognizer: UITapGestureRecognizer = { 42 | let recognizer = UITapGestureRecognizer() 43 | recognizer.numberOfTouchesRequired = 2 44 | recognizer.addTarget(self, action: #selector(handleTap(_:))) 45 | return recognizer 46 | }() 47 | 48 | private lazy var forceRecognizer: ForceTouchGestureRecognizer = { 49 | let recognizer = ForceTouchGestureRecognizer() 50 | recognizer.minimumForceActivation = 0.2 51 | recognizer.addTarget(self, action: #selector(handleForceTouch(_:))) 52 | return recognizer 53 | }() 54 | 55 | 56 | open override func viewDidLoad() { 57 | super.viewDidLoad() 58 | 59 | // Setup initial SCNView 60 | sceneView = SCNView(frame: view.bounds) 61 | sceneView?.rendersContinuously = true 62 | 63 | // Setup code table 64 | codeTable = CodeTableView(style: .plain) 65 | codeTable?.delegate = self 66 | addChild(codeTable!) 67 | view.addSubview(codeTable!.view) 68 | 69 | view.addGestureRecognizer(tapRecognizer) 70 | sceneView?.addGestureRecognizer(forceRecognizer) 71 | } 72 | 73 | open override func viewDidAppear(_ animated: Bool) { 74 | super.viewDidAppear(animated) 75 | 76 | let helperFunctions: [Item] = [.code(Snippet("modFunctions", code: .modFunctions)), 77 | .code(Snippet("circle", code: .circle)), 78 | .code(Snippet("snoise", code: .snoise)), 79 | .code(Snippet("testFunctions", code: .testFunctions)), 80 | .code(Snippet("body", code: .pragmaBody))] 81 | 82 | codeTable?.sections = [Section("Settings", 83 | items: [.geometry(GeometryModel())], 84 | isCollapsed: true), 85 | // Section("Utility", 86 | // items: helperFunctions, 87 | // isCollapsed: true), 88 | // Section("Fragment Shader", 89 | // items: [.code(Snippet("Fragment", code: .fragmentShader, isFragmentShader: true))], 90 | // isCollapsed: false)] 91 | Section("Fragment Shader", 92 | items: [.code(Snippet("Fragment", code: .megaTest2, isFragmentShader: true))], 93 | isCollapsed: false)] 94 | codeTable?.tableView.reloadData() 95 | sceneController?.fragment = codeTable?.shader 96 | } 97 | 98 | @objc private func handleTap(_ recognizer: UITapGestureRecognizer) { 99 | guard let codeView = codeTable?.view else { 100 | return 101 | } 102 | codeView.isHidden = !codeView.isHidden 103 | codeView.endEditing(true) 104 | } 105 | 106 | @objc private func handleForceTouch(_ recognizer: ForceTouchGestureRecognizer) { 107 | switch recognizer.state { 108 | case .began: 109 | TapticEngine.impact.feedback(.light) 110 | 111 | case .changed: 112 | let scale = 1.0 - (0.04 * recognizer.force) 113 | DispatchQueue.main.async { 114 | self.view.transform = CGAffineTransform(scaleX: scale, y: scale) 115 | } 116 | 117 | default: 118 | DispatchQueue.main.async { 119 | UIView.animate(withDuration: 0.15) { 120 | self.view.transform = .identity 121 | } 122 | } 123 | } 124 | 125 | if recognizer.force > 0.99 { 126 | // Stop the controller from tracking force 127 | recognizer.state = .ended 128 | 129 | TapticEngine.impact.feedback(.medium) 130 | 131 | if Recorder.isRecording { 132 | Recorder.stopRecording { [weak self] previewController, _ in 133 | guard let previewController = previewController else { 134 | return 135 | } 136 | previewController.previewControllerDelegate = self 137 | self?.present(previewController, animated: true) 138 | } 139 | } else { 140 | Recorder.startRecording() 141 | } 142 | } 143 | } 144 | 145 | open override func viewDidLayoutSubviews() { 146 | super.viewDidLayoutSubviews() 147 | 148 | sceneView?.frame = view.bounds 149 | } 150 | 151 | } 152 | 153 | extension SceneViewController: CodeTableDelegate { 154 | 155 | public func codeTable(_ table: CodeTableView, didSelect geometry: Geometry) { 156 | sceneController?.geometryType = geometry.type 157 | } 158 | 159 | public func codeTable(_ table: CodeTableView, didUpdate shader: Shader) { 160 | sceneController?.fragment = shader 161 | 162 | if let shader: Snippet = table.sections.items.compactMap({ item -> Snippet? in 163 | switch item { 164 | case .code(let snippet): 165 | guard snippet.isFragmentShader else { 166 | fallthrough 167 | } 168 | return snippet 169 | default: 170 | return nil 171 | } 172 | }).first { 173 | Shader.fragmentShader = shader.code 174 | } 175 | } 176 | 177 | } 178 | 179 | extension SceneViewController: RPPreviewViewControllerDelegate { 180 | 181 | public func previewControllerDidFinish(_ previewController: RPPreviewViewController) { 182 | previewController.dismiss(animated: true) 183 | } 184 | 185 | } 186 | -------------------------------------------------------------------------------- /Shades/Shader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Shader.swift 3 | // Shades 4 | // 5 | // Created by Chris Zelazo on 2/18/19. 6 | // Copyright © 2019 Chris Zelazo. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public typealias Shader = String 12 | public extension Shader { 13 | 14 | static let megaTest: Shader = 15 | """ 16 | float circle(vec2 st, vec2 pos, float radius, float edge) { 17 | st = pos-st; 18 | float r = length(st) * 4.0; 19 | float f = radius; 20 | return 1.0-smoothstep(f-edge, f+edge, r); 21 | } 22 | 23 | float circleb(vec2 st, vec2 pos, float radius, float edge, float w) { 24 | return circle(st,pos,radius,edge) - circle(st,pos,radius-w,edge); 25 | } 26 | 27 | vec3 mod289(vec3 x) { 28 | return x - floor(x * (1.0 / 289.0)) * 289.0; 29 | } 30 | vec2 mod289(vec2 x) { 31 | return x - floor(x * (1.0 / 289.0)) * 289.0; 32 | } 33 | vec3 permute(vec3 x){ 34 | return mod289(((x*34.0)+1.0)*x); 35 | } 36 | 37 | float snoise(vec2 v) { 38 | // Precompute values for skewed triangular grid 39 | const vec4 C = vec4(0.211324865405187, 40 | // (3.0-sqrt(3.0))/6.0 41 | 0.366025403784439, 42 | // 0.5*(sqrt(3.0)-1.0) 43 | -0.577350269189626, 44 | // -1.0 + 2.0 * C.x 45 | 0.024390243902439); 46 | // 1.0 / 41.0 47 | 48 | // First corner (x0) 49 | vec2 i = floor(v + dot(v, C.yy)); 50 | vec2 x0 = v - i + dot(i, C.xx); 51 | 52 | // Other two corners (x1, x2) 53 | vec2 i1 = vec2(0.0); 54 | i1 = (x0.x > x0.y)? vec2(1.0, 0.0):vec2(0.0, 1.0); 55 | vec2 x1 = x0.xy + C.xx - i1; 56 | vec2 x2 = x0.xy + C.zz; 57 | 58 | // Do some permutations to avoid 59 | // truncation effects in permutation 60 | i = mod289(i); 61 | vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0 )); 62 | 63 | vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x1,x1), dot(x2,x2) ), 0.0); 64 | 65 | m = m*m ; 66 | m = m*m ; 67 | 68 | // Gradients: 69 | // 41 pts uniformly over a line, mapped onto a diamond 70 | // The ring size 17*17 = 289 is close to a multiple 71 | // of 41 (41*7 = 287) 72 | 73 | vec3 x = 2.0 * fract(p * C.www) - 1.0; 74 | vec3 h = abs(x) - 0.5; 75 | vec3 ox = floor(x + 0.5); 76 | vec3 a0 = x - ox; 77 | 78 | // Normalise gradients implicitly by scaling m 79 | // Approximation of: m *= inversesqrt(a0*a0 + h*h); 80 | m *= 1.79284291400159 - 0.85373472095314 * (a0*a0+h*h); 81 | 82 | // Compute final noise value at P 83 | vec3 g = vec3(0.0); 84 | g.x = a0.x * x0.x + h.x * x0.y; 85 | g.yz = a0.yz * vec2(x1.x,x2.x) + h.yz * vec2(x1.y,x2.y); 86 | return 130.0 * dot(m, g); 87 | } 88 | 89 | // Colors 90 | 91 | 92 | vec3 pink() { 93 | return vec3(0.98, 0.76, 0.74); 94 | } 95 | 96 | vec3 green() { 97 | return vec3(0.07, 0.40, 0.40); 98 | } 99 | 100 | vec3 blue() { 101 | return vec3(0.12, 0.40, 0.67); 102 | } 103 | 104 | vec3 white() { 105 | return vec3(1.0); 106 | } 107 | 108 | // Shaping 109 | 110 | // Creates a circle centered in uv with a given radius and edge. 111 | float circleMask(vec2 uv, float radius, float edge) { 112 | return circle(uv, vec2(0.), radius, edge); 113 | } 114 | 115 | float rectangle(vec2 uv, float width, float height) { 116 | float hW = width / 2.0; 117 | float hH = height / 2.0; 118 | return (-hW < uv.x && uv.x < hW && 119 | -hH < uv.y && uv.y < hH) ? 1.0 : 0.0; 120 | } 121 | 122 | float square(vec2 uv, float dimension) { 123 | return rectangle(uv, dimension, dimension); 124 | } 125 | 126 | // Grids 127 | 128 | // Creates a hexagonal tiling grid 129 | vec4 hexCoords(vec2 uv) { 130 | // Repeat rate for both axes - 1, sqrt(3) 131 | vec2 r = vec2(1, 1.73); 132 | vec2 h = r*.5; // normalize 133 | 134 | vec2 a = fmod(uv, r)-h; 135 | vec2 b = fmod(uv-h, r)-h; 136 | 137 | // Same as length(a) < length(b) 138 | vec2 gv = dot(a, a) < dot(b,b) ? a : b; 139 | vec2 id = uv-gv; // Unique ids 140 | return vec4(gv.x, gv.y, id.x, id.y); 141 | } 142 | 143 | vec4 hexGrid(vec2 uv, float repeat) { 144 | vec2 _uv = (uv / 2.0) + 0.5; 145 | return hexCoords(_uv * repeat); 146 | } 147 | 148 | // Add background color and foreground shape with color 149 | vec3 shapeMask(float shape, vec3 bgColor, vec3 fgColor) { 150 | float bgMask = 1. - shape; 151 | return bgMask * bgColor + shape * fgColor; 152 | } 153 | 154 | // Creates a single tile of 6 dots in a centered hexagon tiling pattern. 155 | vec3 dotGrid(vec2 uv, float repeat, float dotDimension, vec3 bgColor, vec3 fgColor, bool fading) { 156 | vec4 coords = hexGrid(uv, repeat); 157 | 158 | float normalizedY = coords.w / repeat; // now (0.0 to 1.0) 159 | float dimension = dotDimension; 160 | 161 | if (fading) { 162 | dimension = smoothstep(0.1 * dotDimension, dotDimension, normalizedY * dotDimension) + 0.1; 163 | dimension *= 1.7; 164 | } 165 | 166 | float dot = circle(coords.xy, vec2(0), dimension, .005 * repeat); 167 | return shapeMask(dot, bgColor, fgColor); 168 | } 169 | 170 | vec3 squareGrid(vec2 uv, float repeat, float dimension, vec3 bgColor, vec3 fgColor) { 171 | vec4 coords = hexGrid(uv, repeat); 172 | float shape = square(coords.xy, dimension); 173 | 174 | vec2 id = (coords.zw / repeat) * 10.; 175 | vec2 fid = vec2(floor(id.x), floor(id.y)); 176 | // if (floor(id.x) == 2. && floor(id.y) == 4.) { 177 | // shape = 0.; 178 | // } 179 | 180 | // if (fmod(fid.x, 1.) == 0.) { 181 | // shape = 0.; 182 | // } 183 | 184 | if (fmod(id.x, repeat) == 10./repeat) { 185 | shape = 0.; 186 | } 187 | 188 | return shapeMask(shape, bgColor, vec3(coords.w / repeat)); 189 | } 190 | 191 | #pragma body 192 | 193 | vec2 st = _surface.diffuseTexcoord; 194 | vec2 bounds = u_boundingBox[1].xy; 195 | 196 | float _min = min(bounds.x, bounds.y); 197 | float _max = max(bounds.x, bounds.y); 198 | float ratio = _min / _max; 199 | 200 | if (_min == st.x) { 201 | st.x *= ratio; 202 | st.x += (1.0 - ratio) * 0.5; 203 | } else { 204 | st.y *= ratio; 205 | st.y += (1.0 - ratio) * 0.5; 206 | } 207 | 208 | // UV values goes from -1 to 1 209 | // So we need to remap st (0.0 to 1.0) 210 | 211 | st -= 0.5; // becomes -0.5 to 0.5 212 | st *= 2.0; // becomes -1.0 to 1.0 213 | 214 | vec3 color = vec3(0); 215 | 216 | float noise = snoise(st + scn_frame.time) * 0.395; 217 | 218 | float repeat = 12.; 219 | float dimension = .2; 220 | 221 | vec3 grid; 222 | 223 | grid = dotGrid(st, repeat, dimension, pink(), green(), false); 224 | //grid = squareGrid(st, repeat, dimension, blue(), white()); 225 | 226 | // grid = vec3(hexGrid(st, repeat).x); 227 | 228 | color = grid; 229 | 230 | // Mask with the outer circle 231 | //color *= circleMask(st, 4., 0.02); 232 | 233 | _output.color = vec4(color,1.0); 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | """ 247 | 248 | static let megaTest2: Shader = 249 | """ 250 | 251 | mat2 rotate2d(float _angle){ 252 | return mat2(cos(_angle), -sin(_angle), 253 | sin(_angle), cos(_angle)); 254 | } 255 | 256 | vec3 mint() { 257 | return vec3(0.71, 0.86, 0.73); 258 | } 259 | 260 | vec3 pink() { 261 | return vec3(0.98, 0.76, 0.74); 262 | } 263 | 264 | vec3 rose() { 265 | return vec3(0.93, 0.35, 0.62); 266 | } 267 | 268 | vec3 green() { 269 | return vec3(0.07, 0.40, 0.40); 270 | } 271 | 272 | vec3 blue() { 273 | return vec3(0.12, 0.40, 0.67); 274 | } 275 | 276 | vec3 white() { 277 | return vec3(1.0); 278 | } 279 | 280 | vec3 purple() { 281 | return vec3(0.44, 0.28, 0.49); 282 | } 283 | 284 | vec3 yellowPale() { 285 | return vec3(1.00, 0.91, 0.77); 286 | } 287 | 288 | vec3 yellowEaster() { 289 | return vec3(0.99, 0.95, 0.65); 290 | } 291 | 292 | vec3 goldenrod() { 293 | return vec3(0.97, 0.73, 0.22); 294 | } 295 | 296 | vec3 mod289(vec3 x) { 297 | return x - floor(x * (1.0 / 289.0)) * 289.0; 298 | } 299 | vec2 mod289(vec2 x) { 300 | return x - floor(x * (1.0 / 289.0)) * 289.0; 301 | } 302 | vec3 permute(vec3 x){ 303 | return mod289(((x*34.0)+1.0)*x); 304 | } 305 | 306 | float snoise(vec2 v) { 307 | // Precompute values for skewed triangular grid 308 | const vec4 C = vec4(0.211324865405187, 309 | // (3.0-sqrt(3.0))/6.0 310 | 0.366025403784439, 311 | // 0.5*(sqrt(3.0)-1.0) 312 | -0.577350269189626, 313 | // -1.0 + 2.0 * C.x 314 | 0.024390243902439); 315 | // 1.0 / 41.0 316 | 317 | // First corner (x0) 318 | vec2 i = floor(v + dot(v, C.yy)); 319 | vec2 x0 = v - i + dot(i, C.xx); 320 | 321 | // Other two corners (x1, x2) 322 | vec2 i1 = vec2(0.0); 323 | i1 = (x0.x > x0.y)? vec2(1.0, 0.0):vec2(0.0, 1.0); 324 | vec2 x1 = x0.xy + C.xx - i1; 325 | vec2 x2 = x0.xy + C.zz; 326 | 327 | // Do some permutations to avoid 328 | // truncation effects in permutation 329 | i = mod289(i); 330 | vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0 )); 331 | 332 | vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x1,x1), dot(x2,x2) ), 0.0); 333 | 334 | m = m*m ; 335 | m = m*m ; 336 | 337 | // Gradients: 338 | // 41 pts uniformly over a line, mapped onto a diamond 339 | // The ring size 17*17 = 289 is close to a multiple 340 | // of 41 (41*7 = 287) 341 | 342 | vec3 x = 2.0 * fract(p * C.www) - 1.0; 343 | vec3 h = abs(x) - 0.5; 344 | vec3 ox = floor(x + 0.5); 345 | vec3 a0 = x - ox; 346 | 347 | // Normalise gradients implicitly by scaling m 348 | // Approximation of: m *= inversesqrt(a0*a0 + h*h); 349 | m *= 1.79284291400159 - 0.85373472095314 * (a0*a0+h*h); 350 | 351 | // Compute final noise value at P 352 | vec3 g = vec3(0.0); 353 | g.x = a0.x * x0.x + h.x * x0.y; 354 | g.yz = a0.yz * vec2(x1.x,x2.x) + h.yz * vec2(x1.y,x2.y); 355 | return 130.0 * dot(m, g); 356 | } 357 | 358 | float circle(vec2 st, vec2 pos, float radius, float edge) { 359 | st = pos-st; 360 | float r = length(st) * 4.0; 361 | float f = radius; 362 | return 1.0-smoothstep(f-edge, f+edge, r); 363 | } 364 | 365 | float circleb(vec2 st, vec2 pos, float radius, float edge, float w) { 366 | return circle(st,pos,radius,edge) - circle(st,pos,radius-w,edge); 367 | } 368 | 369 | // Creates a circle centered in uv with a given radius and edge. 370 | float circleMask(vec2 uv, float radius, float edge) { 371 | return circle(uv, vec2(0.), radius, edge); 372 | } 373 | 374 | float rectangle(vec2 uv, float width, float height) { 375 | float hW = width / 2.0; 376 | float hH = height / 2.0; 377 | return (-hW < uv.x && uv.x < hW && 378 | -hH < uv.y && uv.y < hH) ? 1.0 : 0.0; 379 | } 380 | 381 | float square(vec2 uv, float dimension) { 382 | return rectangle(uv, dimension, dimension); 383 | } 384 | 385 | float drawLine(vec2 uv, vec2 p1, vec2 p2, float width) { 386 | float a = abs(distance(p1, uv)); 387 | float b = abs(distance(p2, uv)); 388 | float c = abs(distance(p1, p2)); 389 | 390 | if ( a >= c || b >= c ) return 0.0; 391 | 392 | float p = (a + b + c) * 0.5; 393 | 394 | // median to (p1, p2) vector 395 | float h = 2. / c * sqrt( p * ( p - a) * ( p - b) * ( p - c)); 396 | 397 | return mix(1.0, 0.0, smoothstep(0.5 * width, 1.5 * width, h)); 398 | } 399 | 400 | float vShape(vec2 uv, float width) { 401 | vec2 _uv = (uv / 2.0) + 0.5; 402 | float hWidth = width / 8.; 403 | return max(drawLine(_uv, vec2(0.33, 0.68), vec2(0.5 + hWidth, 0.33), width), 404 | drawLine(_uv, vec2(0.68, 0.68), vec2(0.5 - hWidth, 0.33), width)); 405 | } 406 | 407 | // Creates a hexagonal tiling grid 408 | vec4 hexCoords(vec2 uv) { 409 | // Repeat rate for both axes - 1, sqrt(3) 410 | vec2 r = vec2(1, 1.73); 411 | vec2 h = r*.5; // normalize 412 | 413 | vec2 a = fmod(uv, r)-h; 414 | vec2 b = fmod(uv-h, r)-h; 415 | 416 | // Same as length(a) < length(b) 417 | vec2 gv = dot(a, a) < dot(b,b) ? a : b; 418 | vec2 id = uv-gv; // Unique ids 419 | return vec4(gv.x, gv.y, id.x, id.y); 420 | } 421 | 422 | vec4 hexGrid(vec2 uv, float repeat) { 423 | vec2 _uv = (uv / 2.0) + 0.5; 424 | return hexCoords(_uv * repeat); 425 | } 426 | 427 | // Add background color and foreground shape with color 428 | vec3 shapeMask(float shape, vec3 bgColor, vec3 fgColor) { 429 | float bgMask = 1. - shape; 430 | return bgMask * bgColor + shape * fgColor; 431 | } 432 | 433 | // Creates a single tile of 6 dots in a centered hexagon tiling pattern. 434 | vec3 dotGrid(vec2 uv, float repeat, float dotDimension, vec3 bgColor, vec3 fgColor, bool fading) { 435 | vec4 coords = hexGrid(uv, repeat); 436 | 437 | float normalizedY = coords.w / repeat; // now (0.0 to 1.0) 438 | float dimension = dotDimension; 439 | 440 | if (fading) { 441 | dimension = smoothstep(0.1 * dotDimension, dotDimension, normalizedY * dotDimension) + 0.1; 442 | dimension *= 1.7; 443 | } 444 | 445 | float dot = circle(coords.xy, vec2(0), dimension, .001 * repeat); 446 | return shapeMask(dot, bgColor, fgColor); 447 | } 448 | 449 | vec3 squareGrid(vec2 uv, float repeat, float dimension, vec3 bgColor, vec3 fgColor) { 450 | vec4 coords = hexGrid(uv, repeat); 451 | float shape = square(coords.xy, dimension); 452 | 453 | vec2 id = (coords.zw / repeat) * 10.; 454 | vec2 fid = vec2(floor(id.x), floor(id.y)); 455 | // if (floor(id.x) == 2. && floor(id.y) == 4.) { 456 | // shape = 0.; 457 | // } 458 | 459 | // if (fmod(fid.x, 1.) == 0.) { 460 | // shape = 0.; 461 | // } 462 | 463 | if (fmod(id.x, repeat) == 10./repeat) { 464 | shape = 0.; 465 | } 466 | 467 | return shapeMask(shape, bgColor, fgColor); 468 | } 469 | 470 | vec3 tripleDotGrid(vec2 uv, vec3 bgColor, vec3 fgColor) { 471 | vec3 color = bgColor; 472 | 473 | vec2 _uv = (uv / 2.0) + 0.5; 474 | _uv *= 90.; 475 | _uv = fmod(_uv, vec2(1.6, 1.0)); 476 | 477 | color += vec3(circle(_uv, vec2(0.2, 0.2), 0.5, 0.02)); 478 | color += vec3(circle(_uv, vec2(0.6, 0.2), 0.5, 0.02)); 479 | color += vec3(circle(_uv, vec2(1.0, 0.2), 0.5, 0.02)); 480 | 481 | return color * fgColor; 482 | } 483 | 484 | vec3 vGrid(vec2 uv, float repeat, vec3 bgColor, vec3 fgColor, float time) { 485 | vec3 color = bgColor; 486 | vec4 coords = hexGrid(uv, repeat); 487 | 488 | float pi = 3.1415926535897932384626433832795; 489 | float rotation = snoise(coords.zw + 0.2 * time) * pi; 490 | vec2 rotatedCoords = coords.xy * rotate2d(rotation); 491 | 492 | float shape = vShape(rotatedCoords, 0.02); 493 | return shapeMask(shape, bgColor, fgColor); 494 | } 495 | 496 | #pragma body 497 | 498 | vec2 st = _surface.diffuseTexcoord; 499 | vec2 bounds = u_boundingBox[1].xy; 500 | 501 | float _min = min(bounds.x, bounds.y); 502 | float _max = max(bounds.x, bounds.y); 503 | float ratio = _min / _max; 504 | 505 | if (_min == st.x) { 506 | st.x *= ratio; 507 | st.x += (1.0 - ratio) * 0.5; 508 | } else { 509 | st.y *= ratio; 510 | st.y += (1.0 - ratio) * 0.5; 511 | } 512 | 513 | // UV values goes from -1 to 1 514 | // So we need to remap st (0.0 to 1.0) 515 | 516 | st -= 0.5; // becomes -0.5 to 0.5 517 | st *= 2.0; // becomes -1.0 to 1.0 518 | 519 | vec3 color = mint(); 520 | 521 | float repeat = 50.; 522 | 523 | float numCircles = 6.; 524 | 525 | float radius = 1.2; 526 | float radiusDelta = (radius - 0.2) / numCircles; 527 | 528 | float dimen = 2.; 529 | float dimenDelta = (dimen - 0.2) / numCircles; 530 | 531 | float circOffset = 0.02; 532 | 533 | vec2 pos = vec2(-1.5, 0.); 534 | 535 | for (int i = 0; i < 6; ++i) { 536 | 537 | pos += vec2(radius / 2.0, 0.0); 538 | dimen -= dimenDelta; 539 | radius -= radiusDelta; 540 | 541 | // Yellow solid circle 542 | float circ = circle(st, pos, radius, 0.0); 543 | color = shapeMask(circ, color, goldenrod()); 544 | 545 | // Pink dot circle 546 | vec3 dots2 = dotGrid(st, repeat, dimen, color, rose(), false); 547 | float circ2 = circle(st, pos + vec2(circOffset, -circOffset), radius, 0.0); 548 | color = shapeMask(circ2, color, dots2); 549 | 550 | } 551 | 552 | _output.color = vec4(color,1.0); 553 | 554 | 555 | 556 | 557 | """ 558 | 559 | static let testFunctions: Shader = 560 | """ 561 | // Colors 562 | 563 | vec3 pink() { 564 | return vec3(0.98, 0.76, 0.74); 565 | } 566 | 567 | vec3 green() { 568 | return vec3(0.07, 0.40, 0.40); 569 | } 570 | 571 | vec3 blue() { 572 | return vec3(0.12, 0.40, 0.67); 573 | } 574 | 575 | vec3 white() { 576 | return vec3(1.0); 577 | } 578 | 579 | // Shaping 580 | 581 | // Creates a circle centered in uv with a given radius and edge. 582 | float circleMask(vec2 uv, float radius, float edge) { 583 | return circle(uv, vec2(0.), radius, edge); 584 | } 585 | 586 | float rectangle(vec2 uv, float width, float height) { 587 | float hW = width / 2.0; 588 | float hH = height / 2.0; 589 | return (-hW < uv.x && uv.x < hW && 590 | -hH < uv.y && uv.y < hH) ? 1.0 : 0.0; 591 | } 592 | 593 | float square(vec2 uv, float dimension) { 594 | return rectangle(uv, dimension, dimension); 595 | } 596 | 597 | // Grids 598 | 599 | // Creates a hexagonal tiling grid 600 | vec4 hexCoords(vec2 uv) { 601 | // Repeat rate for both axes - 1, sqrt(3) 602 | vec2 r = vec2(1, 1.73); 603 | vec2 h = r*.5; // normalize 604 | 605 | vec2 a = fmod(uv, r)-h; 606 | vec2 b = fmod(uv-h, r)-h; 607 | 608 | // Same as length(a) < length(b) 609 | vec2 gv = dot(a, a) < dot(b,b) ? a : b; 610 | vec2 id = uv-gv; // Unique ids 611 | return vec4(gv.x, gv.y, id.x, id.y); 612 | } 613 | 614 | vec4 hexGrid(vec2 uv, float repeat) { 615 | vec2 _uv = (uv / 2.0) + 0.5; 616 | return hexCoords(_uv * repeat); 617 | } 618 | 619 | // Add background color and foreground shape with color 620 | vec3 shapeMask(float shape, vec3 bgColor, vec3 fgColor) { 621 | float bgMask = 1. - shape; 622 | return bgMask * bgColor + shape * fgColor; 623 | } 624 | 625 | // Creates a single tile of 6 dots in a centered hexagon tiling pattern. 626 | vec3 dotGrid(vec2 uv, float repeat, float dotDimension, vec3 bgColor, vec3 fgColor, bool fading) { 627 | vec4 coords = hexGrid(uv, repeat); 628 | 629 | float normalizedY = coords.w / repeat; // now (0.0 to 1.0) 630 | float dimension = dotDimension; 631 | 632 | if (fading) { 633 | dimension = smoothstep(0.1 * dotDimension, dotDimension, normalizedY * dotDimension) + 0.1; 634 | dimension *= 1.7; 635 | } 636 | 637 | float dot = circle(coords.xy, vec2(0), dimension, .005 * repeat); 638 | return shapeMask(dot, bgColor, fgColor); 639 | } 640 | 641 | vec3 squareGrid(vec2 uv, float repeat, float dimension, vec3 bgColor, vec3 fgColor) { 642 | vec4 coords = hexGrid(uv, repeat); 643 | float shape = square(coords.xy, dimension); 644 | 645 | vec2 id = (coords.zw / repeat) * 10.; 646 | vec2 fid = vec2(floor(id.x), floor(id.y)); 647 | // if (floor(id.x) == 2. && floor(id.y) == 4.) { 648 | // shape = 0.; 649 | // } 650 | 651 | // if (fmod(fid.x, 1.) == 0.) { 652 | // shape = 0.; 653 | // } 654 | 655 | if (fmod(id.x, repeat) == 10./repeat) { 656 | shape = 0.; 657 | } 658 | 659 | return shapeMask(shape, bgColor, vec3(coords.w / repeat)); 660 | } 661 | """ 662 | 663 | static let modFunctions: Shader = 664 | """ 665 | vec3 mod289(vec3 x) { 666 | return x - floor(x * (1.0 / 289.0)) * 289.0; 667 | } 668 | vec2 mod289(vec2 x) { 669 | return x - floor(x * (1.0 / 289.0)) * 289.0; 670 | } 671 | vec3 permute(vec3 x){ 672 | return mod289(((x*34.0)+1.0)*x); 673 | } 674 | """ 675 | 676 | static let snoise: Shader = 677 | """ 678 | float snoise(vec2 v) { 679 | // Precompute values for skewed triangular grid 680 | const vec4 C = vec4(0.211324865405187, 681 | // (3.0-sqrt(3.0))/6.0 682 | 0.366025403784439, 683 | // 0.5*(sqrt(3.0)-1.0) 684 | -0.577350269189626, 685 | // -1.0 + 2.0 * C.x 686 | 0.024390243902439); 687 | // 1.0 / 41.0 688 | 689 | // First corner (x0) 690 | vec2 i = floor(v + dot(v, C.yy)); 691 | vec2 x0 = v - i + dot(i, C.xx); 692 | 693 | // Other two corners (x1, x2) 694 | vec2 i1 = vec2(0.0); 695 | i1 = (x0.x > x0.y)? vec2(1.0, 0.0):vec2(0.0, 1.0); 696 | vec2 x1 = x0.xy + C.xx - i1; 697 | vec2 x2 = x0.xy + C.zz; 698 | 699 | // Do some permutations to avoid 700 | // truncation effects in permutation 701 | i = mod289(i); 702 | vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0 )); 703 | 704 | vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x1,x1), dot(x2,x2) ), 0.0); 705 | 706 | m = m*m ; 707 | m = m*m ; 708 | 709 | // Gradients: 710 | // 41 pts uniformly over a line, mapped onto a diamond 711 | // The ring size 17*17 = 289 is close to a multiple 712 | // of 41 (41*7 = 287) 713 | 714 | vec3 x = 2.0 * fract(p * C.www) - 1.0; 715 | vec3 h = abs(x) - 0.5; 716 | vec3 ox = floor(x + 0.5); 717 | vec3 a0 = x - ox; 718 | 719 | // Normalise gradients implicitly by scaling m 720 | // Approximation of: m *= inversesqrt(a0*a0 + h*h); 721 | m *= 1.79284291400159 - 0.85373472095314 * (a0*a0+h*h); 722 | 723 | // Compute final noise value at P 724 | vec3 g = vec3(0.0); 725 | g.x = a0.x * x0.x + h.x * x0.y; 726 | g.yz = a0.yz * vec2(x1.x,x2.x) + h.yz * vec2(x1.y,x2.y); 727 | return 130.0 * dot(m, g); 728 | } 729 | """ 730 | 731 | static let circle: Shader = 732 | """ 733 | float circle(vec2 st, vec2 pos, float radius, float edge) { 734 | st = pos-st; 735 | float r = length(st) * 4.0; 736 | float f = radius; 737 | return 1.0-smoothstep(f-edge, f+edge, r); 738 | } 739 | 740 | float circleb(vec2 st, vec2 pos, float radius, float edge, float w) { 741 | return circle(st,pos,radius,edge) - circle(st,pos,radius-w,edge); 742 | } 743 | """ 744 | 745 | static let pragmaBody: Shader = "\n#pragma body\n" 746 | 747 | /* 748 | vec2 coords = _surface.diffuseTexcoord; 749 | vec2 bounds = u_boundingBox[1].xy; 750 | float ratio = bounds.x / bounds.y; 751 | coords.x *= ratio; 752 | coords.x += (1.0 - ratio) * 0.5; 753 | 754 | vec2 pos = vec2(cos(u_time), sin(u_time)); 755 | vec3 color = vec3(circle(coords + pos * 0.2, 0.5, 0, 0.6)); 756 | 757 | _output.color = vec4(color, 1.0); 758 | */ 759 | static let defaultFragementShader: Shader = 760 | """ 761 | vec2 st = _surface.diffuseTexcoord; 762 | vec2 bounds = u_boundingBox[1].xy; 763 | float ratio = bounds.x / bounds.y; 764 | st.x *= ratio; 765 | st.x += (1.0 - ratio) * 0.5; 766 | 767 | vec3 color = vec3(0); 768 | 769 | // UV values goes from -1 to 1 770 | // So we need to remap st (0.0 to 1.0) 771 | st -= 0.5; // becomes -0.5 to 0.5 772 | st *= 2.0; // becomes -1.0 to 1.0 773 | 774 | float noise = snoise(st + scn_frame.time) * 0.395; 775 | 776 | 777 | float repeat = 12.; 778 | float dimension = .2; 779 | 780 | vec3 grid; 781 | 782 | grid = dotGrid(st, repeat, dimension, pink(), green(), false); 783 | //grid = squareGrid(st, repeat, dimension, blue(), white()); 784 | 785 | // grid = vec3(hexGrid(st, repeat).x); 786 | 787 | color = grid; 788 | 789 | // Mask with the outer circle 790 | //color *= circleMask(st, 4., 0.02); 791 | 792 | _output.color = vec4(color,1.0); 793 | """ 794 | 795 | public static var fragmentShader: Shader { 796 | get { 797 | // guard let shader = UserDefaults.standard.string(forKey: userFragmentShaderKey) && shader != "" else { 798 | return .defaultFragementShader 799 | // } 800 | // return shader 801 | } 802 | set { 803 | // UserDefaults.standard.set(newValue, forKey: userFragmentShaderKey) 804 | } 805 | } 806 | 807 | private static let userFragmentShaderKey: String = "userFragmentShader" 808 | 809 | } 810 | 811 | -------------------------------------------------------------------------------- /source-editor/.gitattributes: -------------------------------------------------------------------------------- 1 | SourceEditorView.podspec linguist-vendored 2 | -------------------------------------------------------------------------------- /source-editor/.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | .DS_Store 4 | build/ 5 | *.pbxuser 6 | !default.pbxuser 7 | *.mode1v3 8 | !default.mode1v3 9 | *.mode2v3 10 | !default.mode2v3 11 | *.perspectivev3 12 | !default.perspectivev3 13 | xcuserdata 14 | *.xccheckout 15 | *.moved-aside 16 | DerivedData 17 | *.hmap 18 | *.ipa 19 | *.xcuserstate 20 | 21 | -------------------------------------------------------------------------------- /source-editor/.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Dependencies/SavannaKit"] 2 | path = Dependencies/SavannaKit 3 | url = https://github.com/louisdh/savannakit.git 4 | -------------------------------------------------------------------------------- /source-editor/.swift-version: -------------------------------------------------------------------------------- 1 | 4.1 -------------------------------------------------------------------------------- /source-editor/.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode9.4 3 | branches: 4 | only: 5 | - master 6 | env: 7 | global: 8 | - LC_CTYPE=en_US.UTF-8 9 | - LANG=en_US.UTF-8 10 | - WORKSPACE=SourceEditor.xcworkspace 11 | - IOS_FRAMEWORK_SCHEME="SourceEditor" 12 | - MACOS_FRAMEWORK_SCHEME="SourceEditor" 13 | matrix: 14 | 15 | - DESTINATION="OS=11.4,name=iPad Pro (9.7-inch)" SCHEME="$IOS_FRAMEWORK_SCHEME" 16 | # - DESTINATION="arch=x86_64" SCHEME="$MACOS_FRAMEWORK_SCHEME" 17 | 18 | script: 19 | - set -o pipefail 20 | - xcodebuild -version 21 | - xcodebuild -showsdks 22 | 23 | - travis_retry xcodebuild -workspace "$WORKSPACE" -scheme "$SCHEME" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO build | xcpretty; 24 | 25 | after_success: 26 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then 27 | bash <(curl -s https://codecov.io/bash); 28 | fi -------------------------------------------------------------------------------- /source-editor/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Louis D'hauwe 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 | -------------------------------------------------------------------------------- /source-editor/README.md: -------------------------------------------------------------------------------- 1 | # SourceEditor 2 |

3 | Build Status 4 | Codecov 5 |
6 | Swift 7 | PodVersion 8 | Carthage Compatible 9 | Platform: iOS macOS 10 |
11 | Twitter 12 | Donate via PayPal 13 |

14 | 15 | A native source editor for iOS and macOS. Under the hood, this uses [SavannaKit](https://github.com/louisdh/savannakit). The goal of this project is to provide a very fast source editor, supporting many programming languages, out of the box. Currently, only Swift is supported. But adding support for a new language requires little work. 16 | 17 | ![](readme-resources/ios-example.png) 18 | 19 | ## License 20 | 21 | This project is available under the MIT license. See the LICENSE file for more info. 22 | -------------------------------------------------------------------------------- /source-editor/SourceEditor.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'SourceEditor' 3 | s.version = '1.0.1' 4 | s.license = 'MIT' 5 | s.summary = 'A native source editor for iOS and macOS.' 6 | s.homepage = 'https://github.com/louisdh/source-editor' 7 | s.social_media_url = 'http://twitter.com/LouisDhauwe' 8 | s.authors = { 'Louis D\'hauwe' => 'louisdhauwe@silverfox.be' } 9 | s.source = { :git => 'https://github.com/louisdh/source-editor.git', :tag => s.version } 10 | s.module_name = 'SourceEditor' 11 | 12 | s.ios.deployment_target = '11.0' 13 | s.osx.deployment_target = '10.13' 14 | 15 | s.source_files = 'Sources/**/*.swift' 16 | 17 | s.dependency 'SavannaKit', '~> 0.9' 18 | 19 | end 20 | -------------------------------------------------------------------------------- /source-editor/SourceEditor.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 819E51A4221BE4820012798A /* SavannaKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 819E519D221BE4680012798A /* SavannaKit.framework */; }; 11 | BE09FBF120FFD141002BA501 /* SourceEditor.h in Headers */ = {isa = PBXBuildFile; fileRef = BE09FBEF20FFD141002BA501 /* SourceEditor.h */; settings = {ATTRIBUTES = (Public, ); }; }; 12 | BE09FBF520FFD155002BA501 /* SourceCodeRegexLexer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE09FBF220FFD155002BA501 /* SourceCodeRegexLexer.swift */; }; 13 | BE09FBF720FFD155002BA501 /* SourceCodeToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE09FBF420FFD155002BA501 /* SourceCodeToken.swift */; }; 14 | BED4BCBF210832A40068AEE3 /* SwiftLexer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BED4BCBE210832A40068AEE3 /* SwiftLexer.swift */; }; 15 | BED4BCC1210832FC0068AEE3 /* SourceCodeTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = BED4BCC0210832FC0068AEE3 /* SourceCodeTheme.swift */; }; 16 | BED4BCC3210833210068AEE3 /* DefaultSourceCodeTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = BED4BCC2210833210068AEE3 /* DefaultSourceCodeTheme.swift */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXContainerItemProxy section */ 20 | 819E518D221BE4630012798A /* PBXContainerItemProxy */ = { 21 | isa = PBXContainerItemProxy; 22 | containerPortal = 819E5186221BE4630012798A /* SavannaKit.xcodeproj */; 23 | proxyType = 2; 24 | remoteGlobalIDString = BEDA3F351E380E5D00B5091C; 25 | remoteInfo = "SavannaKit iOS"; 26 | }; 27 | 819E518F221BE4630012798A /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = 819E5186221BE4630012798A /* SavannaKit.xcodeproj */; 30 | proxyType = 2; 31 | remoteGlobalIDString = BE15626E1EFE9E7C00EB3723; 32 | remoteInfo = "SavannaKit macOS"; 33 | }; 34 | 819E5191221BE4630012798A /* PBXContainerItemProxy */ = { 35 | isa = PBXContainerItemProxy; 36 | containerPortal = 819E5186221BE4630012798A /* SavannaKit.xcodeproj */; 37 | proxyType = 2; 38 | remoteGlobalIDString = BEE6D387209A312400E881D9; 39 | remoteInfo = SavannaKitTests; 40 | }; 41 | 819E5193221BE4630012798A /* PBXContainerItemProxy */ = { 42 | isa = PBXContainerItemProxy; 43 | containerPortal = 819E5186221BE4630012798A /* SavannaKit.xcodeproj */; 44 | proxyType = 2; 45 | remoteGlobalIDString = BEF8B64020B88780006B85E9; 46 | remoteInfo = "iOS Example"; 47 | }; 48 | 819E519C221BE4680012798A /* PBXContainerItemProxy */ = { 49 | isa = PBXContainerItemProxy; 50 | containerPortal = 819E5195221BE4680012798A /* SavannaKit.xcodeproj */; 51 | proxyType = 2; 52 | remoteGlobalIDString = BEDA3F351E380E5D00B5091C; 53 | remoteInfo = "SavannaKit iOS"; 54 | }; 55 | 819E519E221BE4680012798A /* PBXContainerItemProxy */ = { 56 | isa = PBXContainerItemProxy; 57 | containerPortal = 819E5195221BE4680012798A /* SavannaKit.xcodeproj */; 58 | proxyType = 2; 59 | remoteGlobalIDString = BE15626E1EFE9E7C00EB3723; 60 | remoteInfo = "SavannaKit macOS"; 61 | }; 62 | 819E51A0221BE4680012798A /* PBXContainerItemProxy */ = { 63 | isa = PBXContainerItemProxy; 64 | containerPortal = 819E5195221BE4680012798A /* SavannaKit.xcodeproj */; 65 | proxyType = 2; 66 | remoteGlobalIDString = BEE6D387209A312400E881D9; 67 | remoteInfo = SavannaKitTests; 68 | }; 69 | 819E51A2221BE4680012798A /* PBXContainerItemProxy */ = { 70 | isa = PBXContainerItemProxy; 71 | containerPortal = 819E5195221BE4680012798A /* SavannaKit.xcodeproj */; 72 | proxyType = 2; 73 | remoteGlobalIDString = BEF8B64020B88780006B85E9; 74 | remoteInfo = "iOS Example"; 75 | }; 76 | /* End PBXContainerItemProxy section */ 77 | 78 | /* Begin PBXFileReference section */ 79 | 819E5186221BE4630012798A /* SavannaKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SavannaKit.xcodeproj; path = ../../savannakit/SavannaKit.xcodeproj; sourceTree = ""; }; 80 | 819E5195221BE4680012798A /* SavannaKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SavannaKit.xcodeproj; path = ../savannakit/SavannaKit.xcodeproj; sourceTree = ""; }; 81 | BE09FBE220FFCF06002BA501 /* SourceEditor.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SourceEditor.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 82 | BE09FBEE20FFD141002BA501 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 83 | BE09FBEF20FFD141002BA501 /* SourceEditor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SourceEditor.h; sourceTree = ""; }; 84 | BE09FBF220FFD155002BA501 /* SourceCodeRegexLexer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SourceCodeRegexLexer.swift; sourceTree = ""; }; 85 | BE09FBF420FFD155002BA501 /* SourceCodeToken.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SourceCodeToken.swift; sourceTree = ""; }; 86 | BE27F55B210BAB47003FEF67 /* SavannaKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SavannaKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 87 | BED4BCBE210832A40068AEE3 /* SwiftLexer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftLexer.swift; sourceTree = ""; }; 88 | BED4BCC0210832FC0068AEE3 /* SourceCodeTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceCodeTheme.swift; sourceTree = ""; }; 89 | BED4BCC2210833210068AEE3 /* DefaultSourceCodeTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultSourceCodeTheme.swift; sourceTree = ""; }; 90 | /* End PBXFileReference section */ 91 | 92 | /* Begin PBXFrameworksBuildPhase section */ 93 | BE09FBDE20FFCF06002BA501 /* Frameworks */ = { 94 | isa = PBXFrameworksBuildPhase; 95 | buildActionMask = 2147483647; 96 | files = ( 97 | 819E51A4221BE4820012798A /* SavannaKit.framework in Frameworks */, 98 | ); 99 | runOnlyForDeploymentPostprocessing = 0; 100 | }; 101 | /* End PBXFrameworksBuildPhase section */ 102 | 103 | /* Begin PBXGroup section */ 104 | 819E5187221BE4630012798A /* Products */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 819E518E221BE4630012798A /* SavannaKit.framework */, 108 | 819E5190221BE4630012798A /* SavannaKit.framework */, 109 | 819E5192221BE4630012798A /* SavannaKitTests.xctest */, 110 | 819E5194221BE4630012798A /* iOS Example.app */, 111 | ); 112 | name = Products; 113 | sourceTree = ""; 114 | }; 115 | 819E5196221BE4680012798A /* Products */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | 819E519D221BE4680012798A /* SavannaKit.framework */, 119 | 819E519F221BE4680012798A /* SavannaKit.framework */, 120 | 819E51A1221BE4680012798A /* SavannaKitTests.xctest */, 121 | 819E51A3221BE4680012798A /* iOS Example.app */, 122 | ); 123 | name = Products; 124 | sourceTree = ""; 125 | }; 126 | BE09FBD820FFCF06002BA501 = { 127 | isa = PBXGroup; 128 | children = ( 129 | 819E5195221BE4680012798A /* SavannaKit.xcodeproj */, 130 | BE09FBED20FFD141002BA501 /* Sources */, 131 | BE09FBE320FFCF06002BA501 /* Products */, 132 | BE27F55A210BAB47003FEF67 /* Frameworks */, 133 | ); 134 | sourceTree = ""; 135 | }; 136 | BE09FBE320FFCF06002BA501 /* Products */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | BE09FBE220FFCF06002BA501 /* SourceEditor.framework */, 140 | ); 141 | name = Products; 142 | sourceTree = ""; 143 | }; 144 | BE09FBED20FFD141002BA501 /* Sources */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | 819E5186221BE4630012798A /* SavannaKit.xcodeproj */, 148 | BED4BCC4210833450068AEE3 /* Themes */, 149 | BED4BCBD210832930068AEE3 /* Languages */, 150 | BE09FBF420FFD155002BA501 /* SourceCodeToken.swift */, 151 | BE09FBF220FFD155002BA501 /* SourceCodeRegexLexer.swift */, 152 | BED4BCC0210832FC0068AEE3 /* SourceCodeTheme.swift */, 153 | BE09FBEE20FFD141002BA501 /* Info.plist */, 154 | BE09FBEF20FFD141002BA501 /* SourceEditor.h */, 155 | ); 156 | path = Sources; 157 | sourceTree = ""; 158 | }; 159 | BE27F55A210BAB47003FEF67 /* Frameworks */ = { 160 | isa = PBXGroup; 161 | children = ( 162 | BE27F55B210BAB47003FEF67 /* SavannaKit.framework */, 163 | ); 164 | name = Frameworks; 165 | sourceTree = ""; 166 | }; 167 | BED4BCBD210832930068AEE3 /* Languages */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | BED4BCBE210832A40068AEE3 /* SwiftLexer.swift */, 171 | ); 172 | path = Languages; 173 | sourceTree = ""; 174 | }; 175 | BED4BCC4210833450068AEE3 /* Themes */ = { 176 | isa = PBXGroup; 177 | children = ( 178 | BED4BCC2210833210068AEE3 /* DefaultSourceCodeTheme.swift */, 179 | ); 180 | path = Themes; 181 | sourceTree = ""; 182 | }; 183 | /* End PBXGroup section */ 184 | 185 | /* Begin PBXHeadersBuildPhase section */ 186 | BE09FBDF20FFCF06002BA501 /* Headers */ = { 187 | isa = PBXHeadersBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | BE09FBF120FFD141002BA501 /* SourceEditor.h in Headers */, 191 | ); 192 | runOnlyForDeploymentPostprocessing = 0; 193 | }; 194 | /* End PBXHeadersBuildPhase section */ 195 | 196 | /* Begin PBXNativeTarget section */ 197 | BE09FBE120FFCF06002BA501 /* SourceEditor */ = { 198 | isa = PBXNativeTarget; 199 | buildConfigurationList = BE09FBEA20FFCF06002BA501 /* Build configuration list for PBXNativeTarget "SourceEditor" */; 200 | buildPhases = ( 201 | BE09FBDD20FFCF06002BA501 /* Sources */, 202 | BE09FBDE20FFCF06002BA501 /* Frameworks */, 203 | BE09FBDF20FFCF06002BA501 /* Headers */, 204 | BE09FBE020FFCF06002BA501 /* Resources */, 205 | ); 206 | buildRules = ( 207 | ); 208 | dependencies = ( 209 | ); 210 | name = SourceEditor; 211 | productName = SourceEditor; 212 | productReference = BE09FBE220FFCF06002BA501 /* SourceEditor.framework */; 213 | productType = "com.apple.product-type.framework"; 214 | }; 215 | /* End PBXNativeTarget section */ 216 | 217 | /* Begin PBXProject section */ 218 | BE09FBD920FFCF06002BA501 /* Project object */ = { 219 | isa = PBXProject; 220 | attributes = { 221 | LastUpgradeCheck = 0940; 222 | ORGANIZATIONNAME = "Silver Fox"; 223 | TargetAttributes = { 224 | BE09FBE120FFCF06002BA501 = { 225 | CreatedOnToolsVersion = 9.4.1; 226 | LastSwiftMigration = 0940; 227 | }; 228 | }; 229 | }; 230 | buildConfigurationList = BE09FBDC20FFCF06002BA501 /* Build configuration list for PBXProject "SourceEditor" */; 231 | compatibilityVersion = "Xcode 9.3"; 232 | developmentRegion = en; 233 | hasScannedForEncodings = 0; 234 | knownRegions = ( 235 | en, 236 | ); 237 | mainGroup = BE09FBD820FFCF06002BA501; 238 | productRefGroup = BE09FBE320FFCF06002BA501 /* Products */; 239 | projectDirPath = ""; 240 | projectReferences = ( 241 | { 242 | ProductGroup = 819E5196221BE4680012798A /* Products */; 243 | ProjectRef = 819E5195221BE4680012798A /* SavannaKit.xcodeproj */; 244 | }, 245 | { 246 | ProductGroup = 819E5187221BE4630012798A /* Products */; 247 | ProjectRef = 819E5186221BE4630012798A /* SavannaKit.xcodeproj */; 248 | }, 249 | ); 250 | projectRoot = ""; 251 | targets = ( 252 | BE09FBE120FFCF06002BA501 /* SourceEditor */, 253 | ); 254 | }; 255 | /* End PBXProject section */ 256 | 257 | /* Begin PBXReferenceProxy section */ 258 | 819E518E221BE4630012798A /* SavannaKit.framework */ = { 259 | isa = PBXReferenceProxy; 260 | fileType = wrapper.framework; 261 | path = SavannaKit.framework; 262 | remoteRef = 819E518D221BE4630012798A /* PBXContainerItemProxy */; 263 | sourceTree = BUILT_PRODUCTS_DIR; 264 | }; 265 | 819E5190221BE4630012798A /* SavannaKit.framework */ = { 266 | isa = PBXReferenceProxy; 267 | fileType = wrapper.framework; 268 | path = SavannaKit.framework; 269 | remoteRef = 819E518F221BE4630012798A /* PBXContainerItemProxy */; 270 | sourceTree = BUILT_PRODUCTS_DIR; 271 | }; 272 | 819E5192221BE4630012798A /* SavannaKitTests.xctest */ = { 273 | isa = PBXReferenceProxy; 274 | fileType = wrapper.cfbundle; 275 | path = SavannaKitTests.xctest; 276 | remoteRef = 819E5191221BE4630012798A /* PBXContainerItemProxy */; 277 | sourceTree = BUILT_PRODUCTS_DIR; 278 | }; 279 | 819E5194221BE4630012798A /* iOS Example.app */ = { 280 | isa = PBXReferenceProxy; 281 | fileType = wrapper.application; 282 | path = "iOS Example.app"; 283 | remoteRef = 819E5193221BE4630012798A /* PBXContainerItemProxy */; 284 | sourceTree = BUILT_PRODUCTS_DIR; 285 | }; 286 | 819E519D221BE4680012798A /* SavannaKit.framework */ = { 287 | isa = PBXReferenceProxy; 288 | fileType = wrapper.framework; 289 | path = SavannaKit.framework; 290 | remoteRef = 819E519C221BE4680012798A /* PBXContainerItemProxy */; 291 | sourceTree = BUILT_PRODUCTS_DIR; 292 | }; 293 | 819E519F221BE4680012798A /* SavannaKit.framework */ = { 294 | isa = PBXReferenceProxy; 295 | fileType = wrapper.framework; 296 | path = SavannaKit.framework; 297 | remoteRef = 819E519E221BE4680012798A /* PBXContainerItemProxy */; 298 | sourceTree = BUILT_PRODUCTS_DIR; 299 | }; 300 | 819E51A1221BE4680012798A /* SavannaKitTests.xctest */ = { 301 | isa = PBXReferenceProxy; 302 | fileType = wrapper.cfbundle; 303 | path = SavannaKitTests.xctest; 304 | remoteRef = 819E51A0221BE4680012798A /* PBXContainerItemProxy */; 305 | sourceTree = BUILT_PRODUCTS_DIR; 306 | }; 307 | 819E51A3221BE4680012798A /* iOS Example.app */ = { 308 | isa = PBXReferenceProxy; 309 | fileType = wrapper.application; 310 | path = "iOS Example.app"; 311 | remoteRef = 819E51A2221BE4680012798A /* PBXContainerItemProxy */; 312 | sourceTree = BUILT_PRODUCTS_DIR; 313 | }; 314 | /* End PBXReferenceProxy section */ 315 | 316 | /* Begin PBXResourcesBuildPhase section */ 317 | BE09FBE020FFCF06002BA501 /* Resources */ = { 318 | isa = PBXResourcesBuildPhase; 319 | buildActionMask = 2147483647; 320 | files = ( 321 | ); 322 | runOnlyForDeploymentPostprocessing = 0; 323 | }; 324 | /* End PBXResourcesBuildPhase section */ 325 | 326 | /* Begin PBXSourcesBuildPhase section */ 327 | BE09FBDD20FFCF06002BA501 /* Sources */ = { 328 | isa = PBXSourcesBuildPhase; 329 | buildActionMask = 2147483647; 330 | files = ( 331 | BED4BCC1210832FC0068AEE3 /* SourceCodeTheme.swift in Sources */, 332 | BED4BCBF210832A40068AEE3 /* SwiftLexer.swift in Sources */, 333 | BE09FBF720FFD155002BA501 /* SourceCodeToken.swift in Sources */, 334 | BE09FBF520FFD155002BA501 /* SourceCodeRegexLexer.swift in Sources */, 335 | BED4BCC3210833210068AEE3 /* DefaultSourceCodeTheme.swift in Sources */, 336 | ); 337 | runOnlyForDeploymentPostprocessing = 0; 338 | }; 339 | /* End PBXSourcesBuildPhase section */ 340 | 341 | /* Begin XCBuildConfiguration section */ 342 | BE09FBE820FFCF06002BA501 /* Debug */ = { 343 | isa = XCBuildConfiguration; 344 | buildSettings = { 345 | ALWAYS_SEARCH_USER_PATHS = NO; 346 | CLANG_ANALYZER_NONNULL = YES; 347 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 348 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 349 | CLANG_CXX_LIBRARY = "libc++"; 350 | CLANG_ENABLE_MODULES = YES; 351 | CLANG_ENABLE_OBJC_ARC = YES; 352 | CLANG_ENABLE_OBJC_WEAK = YES; 353 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 354 | CLANG_WARN_BOOL_CONVERSION = YES; 355 | CLANG_WARN_COMMA = YES; 356 | CLANG_WARN_CONSTANT_CONVERSION = YES; 357 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 358 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 359 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 360 | CLANG_WARN_EMPTY_BODY = YES; 361 | CLANG_WARN_ENUM_CONVERSION = YES; 362 | CLANG_WARN_INFINITE_RECURSION = YES; 363 | CLANG_WARN_INT_CONVERSION = YES; 364 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 365 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 366 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 367 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 368 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 369 | CLANG_WARN_STRICT_PROTOTYPES = YES; 370 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 371 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 372 | CLANG_WARN_UNREACHABLE_CODE = YES; 373 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 374 | CODE_SIGN_IDENTITY = "iPhone Developer"; 375 | COPY_PHASE_STRIP = NO; 376 | CURRENT_PROJECT_VERSION = 1; 377 | DEBUG_INFORMATION_FORMAT = dwarf; 378 | ENABLE_STRICT_OBJC_MSGSEND = YES; 379 | ENABLE_TESTABILITY = YES; 380 | GCC_C_LANGUAGE_STANDARD = gnu11; 381 | GCC_DYNAMIC_NO_PIC = NO; 382 | GCC_NO_COMMON_BLOCKS = YES; 383 | GCC_OPTIMIZATION_LEVEL = 0; 384 | GCC_PREPROCESSOR_DEFINITIONS = ( 385 | "DEBUG=1", 386 | "$(inherited)", 387 | ); 388 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 389 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 390 | GCC_WARN_UNDECLARED_SELECTOR = YES; 391 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 392 | GCC_WARN_UNUSED_FUNCTION = YES; 393 | GCC_WARN_UNUSED_VARIABLE = YES; 394 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 395 | MTL_ENABLE_DEBUG_INFO = YES; 396 | ONLY_ACTIVE_ARCH = YES; 397 | SDKROOT = iphoneos; 398 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 399 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 400 | VERSIONING_SYSTEM = "apple-generic"; 401 | VERSION_INFO_PREFIX = ""; 402 | }; 403 | name = Debug; 404 | }; 405 | BE09FBE920FFCF06002BA501 /* Release */ = { 406 | isa = XCBuildConfiguration; 407 | buildSettings = { 408 | ALWAYS_SEARCH_USER_PATHS = NO; 409 | CLANG_ANALYZER_NONNULL = YES; 410 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 411 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 412 | CLANG_CXX_LIBRARY = "libc++"; 413 | CLANG_ENABLE_MODULES = YES; 414 | CLANG_ENABLE_OBJC_ARC = YES; 415 | CLANG_ENABLE_OBJC_WEAK = YES; 416 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 417 | CLANG_WARN_BOOL_CONVERSION = YES; 418 | CLANG_WARN_COMMA = YES; 419 | CLANG_WARN_CONSTANT_CONVERSION = YES; 420 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 421 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 422 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 423 | CLANG_WARN_EMPTY_BODY = YES; 424 | CLANG_WARN_ENUM_CONVERSION = YES; 425 | CLANG_WARN_INFINITE_RECURSION = YES; 426 | CLANG_WARN_INT_CONVERSION = YES; 427 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 428 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 429 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 430 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 431 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 432 | CLANG_WARN_STRICT_PROTOTYPES = YES; 433 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 434 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 435 | CLANG_WARN_UNREACHABLE_CODE = YES; 436 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 437 | CODE_SIGN_IDENTITY = "iPhone Developer"; 438 | COPY_PHASE_STRIP = NO; 439 | CURRENT_PROJECT_VERSION = 1; 440 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 441 | ENABLE_NS_ASSERTIONS = NO; 442 | ENABLE_STRICT_OBJC_MSGSEND = YES; 443 | GCC_C_LANGUAGE_STANDARD = gnu11; 444 | GCC_NO_COMMON_BLOCKS = YES; 445 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 446 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 447 | GCC_WARN_UNDECLARED_SELECTOR = YES; 448 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 449 | GCC_WARN_UNUSED_FUNCTION = YES; 450 | GCC_WARN_UNUSED_VARIABLE = YES; 451 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 452 | MTL_ENABLE_DEBUG_INFO = NO; 453 | SDKROOT = iphoneos; 454 | SWIFT_COMPILATION_MODE = wholemodule; 455 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 456 | VALIDATE_PRODUCT = YES; 457 | VERSIONING_SYSTEM = "apple-generic"; 458 | VERSION_INFO_PREFIX = ""; 459 | }; 460 | name = Release; 461 | }; 462 | BE09FBEB20FFCF06002BA501 /* Debug */ = { 463 | isa = XCBuildConfiguration; 464 | buildSettings = { 465 | CLANG_ENABLE_MODULES = YES; 466 | CODE_SIGN_IDENTITY = ""; 467 | CODE_SIGN_STYLE = Automatic; 468 | DEFINES_MODULE = YES; 469 | DYLIB_COMPATIBILITY_VERSION = 1; 470 | DYLIB_CURRENT_VERSION = 1; 471 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 472 | INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist"; 473 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 474 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 475 | LD_RUNPATH_SEARCH_PATHS = ( 476 | "$(inherited)", 477 | "@executable_path/Frameworks", 478 | "@loader_path/Frameworks", 479 | ); 480 | PRODUCT_BUNDLE_IDENTIFIER = be.silverfox.SourceEditor; 481 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 482 | SKIP_INSTALL = YES; 483 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 484 | SWIFT_VERSION = 4.0; 485 | TARGETED_DEVICE_FAMILY = "1,2"; 486 | }; 487 | name = Debug; 488 | }; 489 | BE09FBEC20FFCF06002BA501 /* Release */ = { 490 | isa = XCBuildConfiguration; 491 | buildSettings = { 492 | CLANG_ENABLE_MODULES = YES; 493 | CODE_SIGN_IDENTITY = ""; 494 | CODE_SIGN_STYLE = Automatic; 495 | DEFINES_MODULE = YES; 496 | DYLIB_COMPATIBILITY_VERSION = 1; 497 | DYLIB_CURRENT_VERSION = 1; 498 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 499 | INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist"; 500 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 501 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 502 | LD_RUNPATH_SEARCH_PATHS = ( 503 | "$(inherited)", 504 | "@executable_path/Frameworks", 505 | "@loader_path/Frameworks", 506 | ); 507 | PRODUCT_BUNDLE_IDENTIFIER = be.silverfox.SourceEditor; 508 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 509 | SKIP_INSTALL = YES; 510 | SWIFT_VERSION = 4.0; 511 | TARGETED_DEVICE_FAMILY = "1,2"; 512 | }; 513 | name = Release; 514 | }; 515 | /* End XCBuildConfiguration section */ 516 | 517 | /* Begin XCConfigurationList section */ 518 | BE09FBDC20FFCF06002BA501 /* Build configuration list for PBXProject "SourceEditor" */ = { 519 | isa = XCConfigurationList; 520 | buildConfigurations = ( 521 | BE09FBE820FFCF06002BA501 /* Debug */, 522 | BE09FBE920FFCF06002BA501 /* Release */, 523 | ); 524 | defaultConfigurationIsVisible = 0; 525 | defaultConfigurationName = Release; 526 | }; 527 | BE09FBEA20FFCF06002BA501 /* Build configuration list for PBXNativeTarget "SourceEditor" */ = { 528 | isa = XCConfigurationList; 529 | buildConfigurations = ( 530 | BE09FBEB20FFCF06002BA501 /* Debug */, 531 | BE09FBEC20FFCF06002BA501 /* Release */, 532 | ); 533 | defaultConfigurationIsVisible = 0; 534 | defaultConfigurationName = Release; 535 | }; 536 | /* End XCConfigurationList section */ 537 | }; 538 | rootObject = BE09FBD920FFCF06002BA501 /* Project object */; 539 | } 540 | -------------------------------------------------------------------------------- /source-editor/SourceEditor.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /source-editor/SourceEditor.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /source-editor/SourceEditor.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Latest 7 | 8 | 9 | -------------------------------------------------------------------------------- /source-editor/SourceEditor.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /source-editor/SourceEditor.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /source-editor/Sources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /source-editor/Sources/Languages/Python3Lexer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Python3Lexer.swift 3 | // SourceEditor 4 | // 5 | // Created by Stefan Wijnja on 27/07/2018. 6 | // Based on SwiftLexer.swift by Louis D'hauwe. 7 | // Copyright © 2018 Silver Fox. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import SavannaKit 12 | 13 | public class Python3Lexer: SourceCodeRegexLexer { 14 | 15 | public init() { 16 | 17 | } 18 | 19 | lazy var generators: [TokenGenerator] = { 20 | 21 | var generators = [TokenGenerator?]() 22 | // Functions 23 | generators.append(regexGenerator("\\bprint(?=\\()", tokenType: .identifier)) 24 | 25 | generators.append(regexGenerator("(?<=[^a-zA-Z])\\d+", tokenType: .number)) 26 | 27 | generators.append(regexGenerator("\\.\\w+", tokenType: .identifier)) 28 | 29 | let keywords = "False None True and as assert break class continue def del elif else except finally for from global if import in is lambda nonlocal not or pass raise return try while with yield".components(separatedBy: " ") 30 | 31 | generators.append(keywordGenerator(keywords, tokenType: .keyword)) 32 | 33 | // Line comment 34 | generators.append(regexGenerator("#(.*)", tokenType: .comment)) 35 | 36 | // Block comment or multi-line string literal 37 | generators.append(regexGenerator("(\"\"\".*\"\"\")|(\'\'\'.*\'\'\')", options: [.dotMatchesLineSeparators], tokenType: .comment)) 38 | 39 | // Single-line string literal 40 | generators.append(regexGenerator("('.*')|(\".*\")", tokenType: .string)) 41 | 42 | // Editor placeholder 43 | var editorPlaceholderPattern = "(<#)[^\"\\n]*" 44 | editorPlaceholderPattern += "(#>)" 45 | generators.append(regexGenerator(editorPlaceholderPattern, tokenType: .editorPlaceholder)) 46 | 47 | return generators.compactMap( { $0 }) 48 | }() 49 | 50 | public func generators(source: String) -> [TokenGenerator] { 51 | return generators 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /source-editor/Sources/Languages/SwiftLexer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftLexer.swift 3 | // SourceEditor 4 | // 5 | // Created by Louis D'hauwe on 24/07/2018. 6 | // Copyright © 2018 Silver Fox. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SavannaKit 11 | 12 | public class SwiftLexer: SourceCodeRegexLexer { 13 | 14 | public init() { 15 | 16 | } 17 | 18 | lazy var generators: [TokenGenerator] = { 19 | 20 | var generators = [TokenGenerator?]() 21 | 22 | // UI/App Kit 23 | generators.append(regexGenerator("\\b(NS|UI)[A-Z][a-zA-Z]+\\b", tokenType: .identifier)) 24 | 25 | // Functions 26 | 27 | generators.append(regexGenerator("\\b(println|print)(?=\\()", tokenType: .identifier)) 28 | 29 | generators.append(regexGenerator("(?<=(\\s|\\[|,|:))(\\d|\\.|_)+", tokenType: .number)) 30 | 31 | generators.append(regexGenerator("\\.[A-Za-z_]+\\w*", tokenType: .identifier)) 32 | 33 | let keywords = "as associatedtype break case catch class continue convenience default defer deinit else enum extension fallthrough false fileprivate final for func get guard if import in init inout internal is lazy let mutating nil nonmutating open operator override private protocol public repeat required rethrows return required self set static struct subscript super switch throw throws true try typealias unowned var weak where while".components(separatedBy: " ") 34 | 35 | generators.append(keywordGenerator(keywords, tokenType: .keyword)) 36 | 37 | let stdlibIdentifiers = "Any Array AutoreleasingUnsafePointer BidirectionalReverseView Bit Bool CFunctionPointer COpaquePointer CVaListPointer Character CollectionOfOne ConstUnsafePointer ContiguousArray Data Dictionary DictionaryGenerator DictionaryIndex Double EmptyCollection EmptyGenerator EnumerateGenerator FilterCollectionView FilterCollectionViewIndex FilterGenerator FilterSequenceView Float Float80 FloatingPointClassification GeneratorOf GeneratorOfOne GeneratorSequence HeapBuffer HeapBuffer HeapBufferStorage HeapBufferStorageBase ImplicitlyUnwrappedOptional IndexingGenerator Int Int16 Int32 Int64 Int8 IntEncoder LazyBidirectionalCollection LazyForwardCollection LazyRandomAccessCollection LazySequence Less MapCollectionView MapSequenceGenerator MapSequenceView MirrorDisposition ObjectIdentifier OnHeap Optional PermutationGenerator QuickLookObject RandomAccessReverseView Range RangeGenerator RawByte Repeat ReverseBidirectionalIndex Printable ReverseRandomAccessIndex SequenceOf SinkOf Slice StaticString StrideThrough StrideThroughGenerator StrideTo StrideToGenerator String Index UTF8View Index UnicodeScalarView IndexType GeneratorType UTF16View UInt UInt16 UInt32 UInt64 UInt8 UTF16 UTF32 UTF8 UnicodeDecodingResult UnicodeScalar Unmanaged UnsafeArray UnsafeArrayGenerator UnsafeMutableArray UnsafePointer VaListBuilder Header Zip2 ZipGenerator2".components(separatedBy: " ") 38 | 39 | generators.append(keywordGenerator(stdlibIdentifiers, tokenType: .identifier)) 40 | 41 | // Line comment 42 | generators.append(regexGenerator("//(.*)", tokenType: .comment)) 43 | 44 | // Block comment 45 | generators.append(regexGenerator("(/\\*)(.*)(\\*/)", options: [.dotMatchesLineSeparators], tokenType: .comment)) 46 | 47 | // Single-line string literal 48 | generators.append(regexGenerator("(\"|@\")[^\"\\n]*(@\"|\")", tokenType: .string)) 49 | 50 | // Multi-line string literal 51 | generators.append(regexGenerator("(\"\"\")(.*?)(\"\"\")", options: [.dotMatchesLineSeparators], tokenType: .string)) 52 | 53 | // Editor placeholder 54 | var editorPlaceholderPattern = "(<#)[^\"\\n]*" 55 | editorPlaceholderPattern += "(#>)" 56 | generators.append(regexGenerator(editorPlaceholderPattern, tokenType: .editorPlaceholder)) 57 | 58 | return generators.compactMap( { $0 }) 59 | }() 60 | 61 | public func generators(source: String) -> [TokenGenerator] { 62 | return generators 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /source-editor/Sources/SourceCodeRegexLexer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SavannaKit+Swift.swift 3 | // SourceEditor 4 | // 5 | // Created by Louis D'hauwe on 24/07/2018. 6 | // Copyright © 2018 Silver Fox. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SavannaKit 11 | 12 | public protocol SourceCodeRegexLexer: RegexLexer { 13 | 14 | 15 | } 16 | 17 | public extension RegexLexer { 18 | 19 | public func regexGenerator(_ pattern: String, options: NSRegularExpression.Options = [], transformer: @escaping TokenTransformer) -> TokenGenerator? { 20 | 21 | guard let regex = try? NSRegularExpression(pattern: pattern, options: options) else { 22 | return nil 23 | } 24 | 25 | return .regex(RegexTokenGenerator(regularExpression: regex, tokenTransformer: transformer)) 26 | } 27 | 28 | } 29 | 30 | public extension SourceCodeRegexLexer { 31 | 32 | public func regexGenerator(_ pattern: String, options: NSRegularExpression.Options = [], tokenType: SourceCodeTokenType) -> TokenGenerator? { 33 | 34 | return regexGenerator(pattern, options: options, transformer: { (range) -> Token in 35 | return SimpleSourceCodeToken(type: tokenType, range: range) 36 | }) 37 | } 38 | 39 | public func keywordGenerator(_ words: [String], tokenType: SourceCodeTokenType) -> TokenGenerator { 40 | 41 | return .keywords(KeywordTokenGenerator(keywords: words, tokenTransformer: { (range) -> Token in 42 | return SimpleSourceCodeToken(type: tokenType, range: range) 43 | })) 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /source-editor/Sources/SourceCodeTheme.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SourceCodeTheme.swift 3 | // SourceEditor 4 | // 5 | // Created by Louis D'hauwe on 24/07/2018. 6 | // Copyright © 2018 Silver Fox. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SavannaKit 11 | 12 | public protocol SourceCodeTheme: SyntaxColorTheme { 13 | 14 | func color(for syntaxColorType: SourceCodeTokenType) -> Color 15 | 16 | } 17 | 18 | extension SourceCodeTheme { 19 | 20 | public func globalAttributes() -> [NSAttributedStringKey: Any] { 21 | 22 | var attributes = [NSAttributedStringKey: Any]() 23 | 24 | attributes[.font] = font 25 | attributes[.foregroundColor] = Color.white 26 | 27 | return attributes 28 | } 29 | 30 | public func attributes(for token: SavannaKit.Token) -> [NSAttributedStringKey: Any] { 31 | var attributes = [NSAttributedStringKey: Any]() 32 | 33 | if let token = token as? SimpleSourceCodeToken { 34 | attributes[.foregroundColor] = color(for: token.type) 35 | } 36 | 37 | return attributes 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /source-editor/Sources/SourceCodeToken.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SourceCodeToken.swift 3 | // SourceEditor 4 | // 5 | // Created by Louis D'hauwe on 24/07/2018. 6 | // Copyright © 2018 Silver Fox. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SavannaKit 11 | 12 | public enum SourceCodeTokenType { 13 | case plain 14 | case number 15 | case string 16 | case identifier 17 | case keyword 18 | case comment 19 | case editorPlaceholder 20 | } 21 | 22 | protocol SourceCodeToken: Token { 23 | 24 | var type: SourceCodeTokenType { get } 25 | 26 | } 27 | 28 | extension SourceCodeToken { 29 | 30 | var isEditorPlaceholder: Bool { 31 | return type == .editorPlaceholder 32 | } 33 | 34 | var isPlain: Bool { 35 | return type == .plain 36 | } 37 | 38 | } 39 | 40 | struct SimpleSourceCodeToken: SourceCodeToken { 41 | 42 | let type: SourceCodeTokenType 43 | 44 | let range: Range 45 | 46 | } 47 | -------------------------------------------------------------------------------- /source-editor/Sources/SourceEditor.h: -------------------------------------------------------------------------------- 1 | // 2 | // SourceEditor.h 3 | // SourceEditor 4 | // 5 | // Created by Louis D'hauwe on 18/07/2018. 6 | // Copyright © 2018 Silver Fox. All rights reserved. 7 | // 8 | -------------------------------------------------------------------------------- /source-editor/Sources/Themes/DefaultSourceCodeTheme.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultSourceCodeTheme.swift 3 | // SourceEditor 4 | // 5 | // Created by Louis D'hauwe on 24/07/2018. 6 | // Copyright © 2018 Silver Fox. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SavannaKit 11 | 12 | public struct DefaultSourceCodeTheme: SourceCodeTheme { 13 | 14 | public init() { 15 | 16 | } 17 | 18 | private static var lineNumbersColor: Color { 19 | return Color(red: 100/255, green: 100/255, blue: 100/255, alpha: 1.0) 20 | } 21 | 22 | public let lineNumbersStyle: LineNumbersStyle? = LineNumbersStyle(font: Font(name: "Menlo", size: 16)!, textColor: lineNumbersColor) 23 | 24 | public let gutterStyle: GutterStyle = GutterStyle(backgroundColor: Color(red: 21/255.0, green: 22/255, blue: 31/255, alpha: 1.0), minimumWidth: 32) 25 | 26 | public let font = Font(name: "Menlo", size: 15)! 27 | 28 | public let backgroundColor = Color(red: 31/255.0, green: 32/255, blue: 41/255, alpha: 1.0) 29 | 30 | public var caretColor = Color.blue 31 | 32 | public func color(for syntaxColorType: SourceCodeTokenType) -> Color { 33 | 34 | switch syntaxColorType { 35 | case .plain: 36 | return .white 37 | 38 | case .number: 39 | return Color(red: 116/255, green: 109/255, blue: 176/255, alpha: 1.0) 40 | 41 | case .string: 42 | return Color(red: 211/255, green: 35/255, blue: 46/255, alpha: 1.0) 43 | 44 | case .identifier: 45 | return Color(red: 20/255, green: 156/255, blue: 146/255, alpha: 1.0) 46 | 47 | case .keyword: 48 | return Color(red: 215/255, green: 0, blue: 143/255, alpha: 1.0) 49 | 50 | case .comment: 51 | return Color(red: 69.0/255.0, green: 187.0/255.0, blue: 62.0/255.0, alpha: 1.0) 52 | 53 | case .editorPlaceholder: 54 | return backgroundColor 55 | } 56 | 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /source-editor/iOS Example/iOS Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | BE44E42B21086C6800EA7BBD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE44E42A21086C6800EA7BBD /* AppDelegate.swift */; }; 11 | BE44E42D21086C6800EA7BBD /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE44E42C21086C6800EA7BBD /* ViewController.swift */; }; 12 | BE44E43021086C6800EA7BBD /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BE44E42E21086C6800EA7BBD /* Main.storyboard */; }; 13 | BE44E43221086C6900EA7BBD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BE44E43121086C6900EA7BBD /* Assets.xcassets */; }; 14 | BE44E43521086C6900EA7BBD /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BE44E43321086C6900EA7BBD /* LaunchScreen.storyboard */; }; 15 | BE44E43D21086DB000EA7BBD /* SavannaKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BE44E43C21086DB000EA7BBD /* SavannaKit.framework */; }; 16 | BE44E43E21086DB000EA7BBD /* SavannaKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BE44E43C21086DB000EA7BBD /* SavannaKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 17 | BE44E44021086DB000EA7BBD /* SourceEditor.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BE44E43F21086DB000EA7BBD /* SourceEditor.framework */; }; 18 | BE44E44121086DB000EA7BBD /* SourceEditor.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BE44E43F21086DB000EA7BBD /* SourceEditor.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXCopyFilesBuildPhase section */ 22 | BE44E44221086DB100EA7BBD /* Embed Frameworks */ = { 23 | isa = PBXCopyFilesBuildPhase; 24 | buildActionMask = 2147483647; 25 | dstPath = ""; 26 | dstSubfolderSpec = 10; 27 | files = ( 28 | BE44E44121086DB000EA7BBD /* SourceEditor.framework in Embed Frameworks */, 29 | BE44E43E21086DB000EA7BBD /* SavannaKit.framework in Embed Frameworks */, 30 | ); 31 | name = "Embed Frameworks"; 32 | runOnlyForDeploymentPostprocessing = 0; 33 | }; 34 | /* End PBXCopyFilesBuildPhase section */ 35 | 36 | /* Begin PBXFileReference section */ 37 | BE44E42721086C6800EA7BBD /* iOS Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "iOS Example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 38 | BE44E42A21086C6800EA7BBD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 39 | BE44E42C21086C6800EA7BBD /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 40 | BE44E42F21086C6800EA7BBD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 41 | BE44E43121086C6900EA7BBD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 42 | BE44E43421086C6900EA7BBD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 43 | BE44E43621086C6900EA7BBD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 44 | BE44E43C21086DB000EA7BBD /* SavannaKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SavannaKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | BE44E43F21086DB000EA7BBD /* SourceEditor.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SourceEditor.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 46 | /* End PBXFileReference section */ 47 | 48 | /* Begin PBXFrameworksBuildPhase section */ 49 | BE44E42421086C6800EA7BBD /* Frameworks */ = { 50 | isa = PBXFrameworksBuildPhase; 51 | buildActionMask = 2147483647; 52 | files = ( 53 | BE44E44021086DB000EA7BBD /* SourceEditor.framework in Frameworks */, 54 | BE44E43D21086DB000EA7BBD /* SavannaKit.framework in Frameworks */, 55 | ); 56 | runOnlyForDeploymentPostprocessing = 0; 57 | }; 58 | /* End PBXFrameworksBuildPhase section */ 59 | 60 | /* Begin PBXGroup section */ 61 | BE44E41E21086C6800EA7BBD = { 62 | isa = PBXGroup; 63 | children = ( 64 | BE44E43F21086DB000EA7BBD /* SourceEditor.framework */, 65 | BE44E43C21086DB000EA7BBD /* SavannaKit.framework */, 66 | BE44E42921086C6800EA7BBD /* iOS Example */, 67 | BE44E42821086C6800EA7BBD /* Products */, 68 | ); 69 | sourceTree = ""; 70 | }; 71 | BE44E42821086C6800EA7BBD /* Products */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | BE44E42721086C6800EA7BBD /* iOS Example.app */, 75 | ); 76 | name = Products; 77 | sourceTree = ""; 78 | }; 79 | BE44E42921086C6800EA7BBD /* iOS Example */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | BE44E42A21086C6800EA7BBD /* AppDelegate.swift */, 83 | BE44E42C21086C6800EA7BBD /* ViewController.swift */, 84 | BE44E42E21086C6800EA7BBD /* Main.storyboard */, 85 | BE44E43121086C6900EA7BBD /* Assets.xcassets */, 86 | BE44E43321086C6900EA7BBD /* LaunchScreen.storyboard */, 87 | BE44E43621086C6900EA7BBD /* Info.plist */, 88 | ); 89 | path = "iOS Example"; 90 | sourceTree = ""; 91 | }; 92 | /* End PBXGroup section */ 93 | 94 | /* Begin PBXNativeTarget section */ 95 | BE44E42621086C6800EA7BBD /* iOS Example */ = { 96 | isa = PBXNativeTarget; 97 | buildConfigurationList = BE44E43921086C6900EA7BBD /* Build configuration list for PBXNativeTarget "iOS Example" */; 98 | buildPhases = ( 99 | BE44E42321086C6800EA7BBD /* Sources */, 100 | BE44E42421086C6800EA7BBD /* Frameworks */, 101 | BE44E42521086C6800EA7BBD /* Resources */, 102 | BE44E44221086DB100EA7BBD /* Embed Frameworks */, 103 | ); 104 | buildRules = ( 105 | ); 106 | dependencies = ( 107 | ); 108 | name = "iOS Example"; 109 | productName = "iOS Example"; 110 | productReference = BE44E42721086C6800EA7BBD /* iOS Example.app */; 111 | productType = "com.apple.product-type.application"; 112 | }; 113 | /* End PBXNativeTarget section */ 114 | 115 | /* Begin PBXProject section */ 116 | BE44E41F21086C6800EA7BBD /* Project object */ = { 117 | isa = PBXProject; 118 | attributes = { 119 | LastSwiftUpdateCheck = 0940; 120 | LastUpgradeCheck = 0940; 121 | ORGANIZATIONNAME = "Silver Fox"; 122 | TargetAttributes = { 123 | BE44E42621086C6800EA7BBD = { 124 | CreatedOnToolsVersion = 9.4.1; 125 | }; 126 | }; 127 | }; 128 | buildConfigurationList = BE44E42221086C6800EA7BBD /* Build configuration list for PBXProject "iOS Example" */; 129 | compatibilityVersion = "Xcode 9.3"; 130 | developmentRegion = en; 131 | hasScannedForEncodings = 0; 132 | knownRegions = ( 133 | en, 134 | Base, 135 | ); 136 | mainGroup = BE44E41E21086C6800EA7BBD; 137 | productRefGroup = BE44E42821086C6800EA7BBD /* Products */; 138 | projectDirPath = ""; 139 | projectRoot = ""; 140 | targets = ( 141 | BE44E42621086C6800EA7BBD /* iOS Example */, 142 | ); 143 | }; 144 | /* End PBXProject section */ 145 | 146 | /* Begin PBXResourcesBuildPhase section */ 147 | BE44E42521086C6800EA7BBD /* Resources */ = { 148 | isa = PBXResourcesBuildPhase; 149 | buildActionMask = 2147483647; 150 | files = ( 151 | BE44E43521086C6900EA7BBD /* LaunchScreen.storyboard in Resources */, 152 | BE44E43221086C6900EA7BBD /* Assets.xcassets in Resources */, 153 | BE44E43021086C6800EA7BBD /* Main.storyboard in Resources */, 154 | ); 155 | runOnlyForDeploymentPostprocessing = 0; 156 | }; 157 | /* End PBXResourcesBuildPhase section */ 158 | 159 | /* Begin PBXSourcesBuildPhase section */ 160 | BE44E42321086C6800EA7BBD /* Sources */ = { 161 | isa = PBXSourcesBuildPhase; 162 | buildActionMask = 2147483647; 163 | files = ( 164 | BE44E42D21086C6800EA7BBD /* ViewController.swift in Sources */, 165 | BE44E42B21086C6800EA7BBD /* AppDelegate.swift in Sources */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXSourcesBuildPhase section */ 170 | 171 | /* Begin PBXVariantGroup section */ 172 | BE44E42E21086C6800EA7BBD /* Main.storyboard */ = { 173 | isa = PBXVariantGroup; 174 | children = ( 175 | BE44E42F21086C6800EA7BBD /* Base */, 176 | ); 177 | name = Main.storyboard; 178 | sourceTree = ""; 179 | }; 180 | BE44E43321086C6900EA7BBD /* LaunchScreen.storyboard */ = { 181 | isa = PBXVariantGroup; 182 | children = ( 183 | BE44E43421086C6900EA7BBD /* Base */, 184 | ); 185 | name = LaunchScreen.storyboard; 186 | sourceTree = ""; 187 | }; 188 | /* End PBXVariantGroup section */ 189 | 190 | /* Begin XCBuildConfiguration section */ 191 | BE44E43721086C6900EA7BBD /* Debug */ = { 192 | isa = XCBuildConfiguration; 193 | buildSettings = { 194 | ALWAYS_SEARCH_USER_PATHS = NO; 195 | CLANG_ANALYZER_NONNULL = YES; 196 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 197 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 198 | CLANG_CXX_LIBRARY = "libc++"; 199 | CLANG_ENABLE_MODULES = YES; 200 | CLANG_ENABLE_OBJC_ARC = YES; 201 | CLANG_ENABLE_OBJC_WEAK = YES; 202 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 203 | CLANG_WARN_BOOL_CONVERSION = YES; 204 | CLANG_WARN_COMMA = YES; 205 | CLANG_WARN_CONSTANT_CONVERSION = YES; 206 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 207 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 208 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 209 | CLANG_WARN_EMPTY_BODY = YES; 210 | CLANG_WARN_ENUM_CONVERSION = YES; 211 | CLANG_WARN_INFINITE_RECURSION = YES; 212 | CLANG_WARN_INT_CONVERSION = YES; 213 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 214 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 215 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 216 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 217 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 218 | CLANG_WARN_STRICT_PROTOTYPES = YES; 219 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 220 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 221 | CLANG_WARN_UNREACHABLE_CODE = YES; 222 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 223 | CODE_SIGN_IDENTITY = "iPhone Developer"; 224 | COPY_PHASE_STRIP = NO; 225 | DEBUG_INFORMATION_FORMAT = dwarf; 226 | ENABLE_STRICT_OBJC_MSGSEND = YES; 227 | ENABLE_TESTABILITY = YES; 228 | GCC_C_LANGUAGE_STANDARD = gnu11; 229 | GCC_DYNAMIC_NO_PIC = NO; 230 | GCC_NO_COMMON_BLOCKS = YES; 231 | GCC_OPTIMIZATION_LEVEL = 0; 232 | GCC_PREPROCESSOR_DEFINITIONS = ( 233 | "DEBUG=1", 234 | "$(inherited)", 235 | ); 236 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 237 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 238 | GCC_WARN_UNDECLARED_SELECTOR = YES; 239 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 240 | GCC_WARN_UNUSED_FUNCTION = YES; 241 | GCC_WARN_UNUSED_VARIABLE = YES; 242 | IPHONEOS_DEPLOYMENT_TARGET = 11.4; 243 | MTL_ENABLE_DEBUG_INFO = YES; 244 | ONLY_ACTIVE_ARCH = YES; 245 | SDKROOT = iphoneos; 246 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 247 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 248 | }; 249 | name = Debug; 250 | }; 251 | BE44E43821086C6900EA7BBD /* Release */ = { 252 | isa = XCBuildConfiguration; 253 | buildSettings = { 254 | ALWAYS_SEARCH_USER_PATHS = NO; 255 | CLANG_ANALYZER_NONNULL = YES; 256 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 257 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 258 | CLANG_CXX_LIBRARY = "libc++"; 259 | CLANG_ENABLE_MODULES = YES; 260 | CLANG_ENABLE_OBJC_ARC = YES; 261 | CLANG_ENABLE_OBJC_WEAK = YES; 262 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 263 | CLANG_WARN_BOOL_CONVERSION = YES; 264 | CLANG_WARN_COMMA = YES; 265 | CLANG_WARN_CONSTANT_CONVERSION = YES; 266 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 267 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 268 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 269 | CLANG_WARN_EMPTY_BODY = YES; 270 | CLANG_WARN_ENUM_CONVERSION = YES; 271 | CLANG_WARN_INFINITE_RECURSION = YES; 272 | CLANG_WARN_INT_CONVERSION = YES; 273 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 274 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 275 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 276 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 277 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 278 | CLANG_WARN_STRICT_PROTOTYPES = YES; 279 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 280 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 281 | CLANG_WARN_UNREACHABLE_CODE = YES; 282 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 283 | CODE_SIGN_IDENTITY = "iPhone Developer"; 284 | COPY_PHASE_STRIP = NO; 285 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 286 | ENABLE_NS_ASSERTIONS = NO; 287 | ENABLE_STRICT_OBJC_MSGSEND = YES; 288 | GCC_C_LANGUAGE_STANDARD = gnu11; 289 | GCC_NO_COMMON_BLOCKS = YES; 290 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 291 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 292 | GCC_WARN_UNDECLARED_SELECTOR = YES; 293 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 294 | GCC_WARN_UNUSED_FUNCTION = YES; 295 | GCC_WARN_UNUSED_VARIABLE = YES; 296 | IPHONEOS_DEPLOYMENT_TARGET = 11.4; 297 | MTL_ENABLE_DEBUG_INFO = NO; 298 | SDKROOT = iphoneos; 299 | SWIFT_COMPILATION_MODE = wholemodule; 300 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 301 | VALIDATE_PRODUCT = YES; 302 | }; 303 | name = Release; 304 | }; 305 | BE44E43A21086C6900EA7BBD /* Debug */ = { 306 | isa = XCBuildConfiguration; 307 | buildSettings = { 308 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 309 | CODE_SIGN_STYLE = Automatic; 310 | DEVELOPMENT_TEAM = 6G5LMQ72D8; 311 | INFOPLIST_FILE = "iOS Example/Info.plist"; 312 | LD_RUNPATH_SEARCH_PATHS = ( 313 | "$(inherited)", 314 | "@executable_path/Frameworks", 315 | ); 316 | PRODUCT_BUNDLE_IDENTIFIER = "be.silverfox.iOS-Example"; 317 | PRODUCT_NAME = "$(TARGET_NAME)"; 318 | SWIFT_VERSION = 4.0; 319 | TARGETED_DEVICE_FAMILY = "1,2"; 320 | }; 321 | name = Debug; 322 | }; 323 | BE44E43B21086C6900EA7BBD /* Release */ = { 324 | isa = XCBuildConfiguration; 325 | buildSettings = { 326 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 327 | CODE_SIGN_STYLE = Automatic; 328 | DEVELOPMENT_TEAM = 6G5LMQ72D8; 329 | INFOPLIST_FILE = "iOS Example/Info.plist"; 330 | LD_RUNPATH_SEARCH_PATHS = ( 331 | "$(inherited)", 332 | "@executable_path/Frameworks", 333 | ); 334 | PRODUCT_BUNDLE_IDENTIFIER = "be.silverfox.iOS-Example"; 335 | PRODUCT_NAME = "$(TARGET_NAME)"; 336 | SWIFT_VERSION = 4.0; 337 | TARGETED_DEVICE_FAMILY = "1,2"; 338 | }; 339 | name = Release; 340 | }; 341 | /* End XCBuildConfiguration section */ 342 | 343 | /* Begin XCConfigurationList section */ 344 | BE44E42221086C6800EA7BBD /* Build configuration list for PBXProject "iOS Example" */ = { 345 | isa = XCConfigurationList; 346 | buildConfigurations = ( 347 | BE44E43721086C6900EA7BBD /* Debug */, 348 | BE44E43821086C6900EA7BBD /* Release */, 349 | ); 350 | defaultConfigurationIsVisible = 0; 351 | defaultConfigurationName = Release; 352 | }; 353 | BE44E43921086C6900EA7BBD /* Build configuration list for PBXNativeTarget "iOS Example" */ = { 354 | isa = XCConfigurationList; 355 | buildConfigurations = ( 356 | BE44E43A21086C6900EA7BBD /* Debug */, 357 | BE44E43B21086C6900EA7BBD /* Release */, 358 | ); 359 | defaultConfigurationIsVisible = 0; 360 | defaultConfigurationName = Release; 361 | }; 362 | /* End XCConfigurationList section */ 363 | }; 364 | rootObject = BE44E41F21086C6800EA7BBD /* Project object */; 365 | } 366 | -------------------------------------------------------------------------------- /source-editor/iOS Example/iOS Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /source-editor/iOS Example/iOS Example.xcodeproj/xcshareddata/xcschemes/iOS Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /source-editor/iOS Example/iOS Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // iOS Example 4 | // 5 | // Created by Louis D'hauwe on 25/07/2018. 6 | // Copyright © 2018 Silver Fox. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /source-editor/iOS Example/iOS Example/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /source-editor/iOS Example/iOS Example/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /source-editor/iOS Example/iOS Example/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 | -------------------------------------------------------------------------------- /source-editor/iOS Example/iOS Example/Base.lproj/Main.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 | Hello world 29 | */ 30 | 31 | let name = <#my name#> 32 | 33 | // Multi-line strings are awesome! 34 | """ 35 | { 36 | "isAwesome": true 37 | } 38 | """ 39 | 40 | guard !name.isEmpty else { 41 | return 42 | } 43 | 44 | print("My name is " + name) 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 95 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /source-editor/iOS Example/iOS Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /source-editor/iOS Example/iOS Example/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // iOS Example 4 | // 5 | // Created by Louis D'hauwe on 25/07/2018. 6 | // Copyright © 2018 Silver Fox. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SavannaKit 11 | import SourceEditor 12 | 13 | class ViewController: UIViewController { 14 | 15 | let lexer = SwiftLexer() 16 | 17 | @IBOutlet weak var syntaxTextView: SyntaxTextView! 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | 22 | syntaxTextView.theme = DefaultSourceCodeTheme() 23 | syntaxTextView.delegate = self 24 | 25 | } 26 | 27 | 28 | } 29 | 30 | extension ViewController: SyntaxTextViewDelegate { 31 | 32 | func didChangeText(_ syntaxTextView: SyntaxTextView) { 33 | 34 | 35 | } 36 | 37 | func didChangeSelectedRange(_ syntaxTextView: SyntaxTextView, selectedRange: NSRange) { 38 | 39 | 40 | } 41 | 42 | func lexerForSource(_ source: String) -> Lexer { 43 | return lexer 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /source-editor/readme-resources/ios-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrszlz/DarthShader/1c21c7e8978ba4a546436624a6a3dd412c26c519/source-editor/readme-resources/ios-example.png --------------------------------------------------------------------------------