├── .arcconfig ├── .arclint ├── .gitignore ├── .gitmodules ├── .travis.yml ├── OhaiPrototope.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── OhaiPrototope.xcscheme ├── OhaiPrototope ├── AppDelegate.swift ├── Base.lproj │ └── LaunchScreen.xib ├── ForcePull.swift ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── cat.imageset │ │ ├── Contents.json │ │ └── cat.png │ ├── cloud.imageset │ │ ├── Contents.json │ │ ├── cloud.png │ │ └── cloud@2x.png │ ├── drop.imageset │ │ ├── Contents.json │ │ ├── drop.png │ │ └── drop@2x.png │ ├── sparkles.imageset │ │ ├── Contents.json │ │ ├── sparkles.png │ │ └── sparkles@2x.png │ ├── star.imageset │ │ ├── Contents.json │ │ └── star.png │ └── unicorn.imageset │ │ ├── Contents.json │ │ └── unicorn-1.png ├── Info.plist ├── LayoutScene.swift ├── RotationGesturePlaypen.swift ├── SceneIndex.swift ├── SceneListingViewController.swift ├── SceneViewController.swift ├── ScrollScene.swift ├── ShadowLightsource.swift ├── ShapeScene.swift ├── SpeechyScene.swift ├── ThrowCats.swift ├── TouchAnimators.swift ├── TouchEvents.swift ├── TouchParticles.swift ├── TouchUnicorns.swift ├── TreeMaker.swift ├── TunableValues.json ├── VideoScene.swift └── jeff.mp4 ├── OhaiPrototopeTests ├── Info.plist └── OhaiPrototopeTests.swift ├── readme.md └── script ├── LICENSE.md ├── README.md ├── bootstrap ├── cibuild ├── schemes.awk ├── targets.awk ├── xcodebuild.awk └── xctool.awk /.arcconfig: -------------------------------------------------------------------------------- 1 | { 2 | "conduit_uri": "https://phabricator.khanacademy.org/", 3 | "lint.engine": "ArcanistConfigurationDrivenLintEngine" 4 | } 5 | -------------------------------------------------------------------------------- /.arclint: -------------------------------------------------------------------------------- 1 | { 2 | "linters": { 3 | "khan-linter": { 4 | "type": "script-and-regex", 5 | "script-and-regex.script": "ka-lint --always-exit-0 --blacklist=yes --propose-arc-fixes", 6 | "script-and-regex.regex": "\/^((?P[^:]*):(?P\\d+):((?P\\d+):)? (?P((?PE)|(?PW))\\S+) (?P[^\\x00]*)(\\x00(?P[^\\x00]*)\\x00(?P[^\\x00]*)\\x00)?)|(?PSKIPPING.*)$\/m" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | *.pbxuser 3 | !default.pbxuser 4 | *.mode1v3 5 | !default.mode1v3 6 | *.mode2v3 7 | !default.mode2v3 8 | *.perspectivev3 9 | !default.perspectivev3 10 | xcuserdata 11 | *.xccheckout 12 | *.moved-aside 13 | DerivedData 14 | *.xcuserstate 15 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "prototope"] 2 | path = prototope 3 | url = https://github.com/khan/prototope 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode611 3 | before_install: true 4 | install: true 5 | git: 6 | submodules: false 7 | script: script/cibuild OhaiPrototope -------------------------------------------------------------------------------- /OhaiPrototope.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6972003C1A80A631009AB8D7 /* TouchUnicorns.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6972003B1A80A631009AB8D7 /* TouchUnicorns.swift */; }; 11 | 697200581A8347F0009AB8D7 /* ThrowCats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 697200571A8347F0009AB8D7 /* ThrowCats.swift */; }; 12 | 756AEA611A7794280039E8AB /* RotationGesturePlaypen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 756AEA601A7794280039E8AB /* RotationGesturePlaypen.swift */; }; 13 | 7843BABC1A89A0EF00DC203F /* ForcePull.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7843BABB1A89A0EF00DC203F /* ForcePull.swift */; }; 14 | 8D7E00291A6238A10059EC34 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D7E00281A6238A10059EC34 /* AppDelegate.swift */; }; 15 | 8D7E002B1A6238A10059EC34 /* SceneViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D7E002A1A6238A10059EC34 /* SceneViewController.swift */; }; 16 | 8D7E00301A6238A10059EC34 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8D7E002F1A6238A10059EC34 /* Images.xcassets */; }; 17 | 8D7E00331A6238A10059EC34 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8D7E00311A6238A10059EC34 /* LaunchScreen.xib */; }; 18 | 8D7E003F1A6238A10059EC34 /* OhaiPrototopeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D7E003E1A6238A10059EC34 /* OhaiPrototopeTests.swift */; }; 19 | 8D7E00571A623A3C0059EC34 /* Prototope.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8D7E00501A6239440059EC34 /* Prototope.framework */; }; 20 | 8D7E005D1A623C140059EC34 /* TouchEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D7E005C1A623C140059EC34 /* TouchEvents.swift */; }; 21 | 8D7E005F1A623F850059EC34 /* Prototope.framework in Copy Files */ = {isa = PBXBuildFile; fileRef = 8D7E00501A6239440059EC34 /* Prototope.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 22 | 8DC95FA51A69E05000D18D30 /* ShadowLightsource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DC95FA41A69E05000D18D30 /* ShadowLightsource.swift */; }; 23 | 8DEBACDF1A7326C5003CF491 /* TouchAnimators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DEBACDE1A7326C5003CF491 /* TouchAnimators.swift */; }; 24 | 9619D3AE1A8E57780022DC54 /* LayoutScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9619D3AD1A8E57780022DC54 /* LayoutScene.swift */; }; 25 | 961AC1FC1A93B99A00ED9744 /* SpeechyScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961AC1FB1A93B99A00ED9744 /* SpeechyScene.swift */; }; 26 | 9632F6511A8D4C6A003D386B /* ScrollScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9632F6501A8D4C6A003D386B /* ScrollScene.swift */; }; 27 | 969933171A893E0F0036684E /* VideoScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 969933161A893E0F0036684E /* VideoScene.swift */; }; 28 | 969933201A895F3E0036684E /* jeff.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 9699331F1A895E980036684E /* jeff.mp4 */; }; 29 | CD11D8651A817DDA004F1197 /* TouchParticles.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD11D8641A817DDA004F1197 /* TouchParticles.swift */; }; 30 | CD4C5E2A1AC5BC64002288EB /* ShapeScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD4C5E291AC5BC64002288EB /* ShapeScene.swift */; }; 31 | EB06C1021A853FC50095489F /* SceneListingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB06C1011A853FC50095489F /* SceneListingViewController.swift */; }; 32 | EB06C1041A8540B80095489F /* SceneIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB06C1031A8540B80095489F /* SceneIndex.swift */; }; 33 | EB190C211A66E85C00F970E1 /* TunableValues.json in Resources */ = {isa = PBXBuildFile; fileRef = EB190C201A66E85C00F970E1 /* TunableValues.json */; }; 34 | EB9557FA1AE71A4D00ABCF9B /* TreeMaker.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB9557F91AE71A4D00ABCF9B /* TreeMaker.swift */; }; 35 | /* End PBXBuildFile section */ 36 | 37 | /* Begin PBXContainerItemProxy section */ 38 | 8D7E00391A6238A10059EC34 /* PBXContainerItemProxy */ = { 39 | isa = PBXContainerItemProxy; 40 | containerPortal = 8D7E001B1A6238A10059EC34 /* Project object */; 41 | proxyType = 1; 42 | remoteGlobalIDString = 8D7E00221A6238A10059EC34; 43 | remoteInfo = OhaiPrototope; 44 | }; 45 | 8D7E004F1A6239440059EC34 /* PBXContainerItemProxy */ = { 46 | isa = PBXContainerItemProxy; 47 | containerPortal = 8D7E00481A6239440059EC34 /* Prototope.xcodeproj */; 48 | proxyType = 2; 49 | remoteGlobalIDString = EBE3A75619DF79C400A77736; 50 | remoteInfo = Prototope; 51 | }; 52 | 8D7E00511A6239440059EC34 /* PBXContainerItemProxy */ = { 53 | isa = PBXContainerItemProxy; 54 | containerPortal = 8D7E00481A6239440059EC34 /* Prototope.xcodeproj */; 55 | proxyType = 2; 56 | remoteGlobalIDString = EB306C0919DF77CC00113FA3; 57 | remoteInfo = PrototopeTestApp; 58 | }; 59 | 8D7E00531A6239440059EC34 /* PBXContainerItemProxy */ = { 60 | isa = PBXContainerItemProxy; 61 | containerPortal = 8D7E00481A6239440059EC34 /* Prototope.xcodeproj */; 62 | proxyType = 2; 63 | remoteGlobalIDString = EB1C2A6C19E4D19B000DC2C1; 64 | remoteInfo = PrototopeTests; 65 | }; 66 | 8D7E00591A623A3C0059EC34 /* PBXContainerItemProxy */ = { 67 | isa = PBXContainerItemProxy; 68 | containerPortal = 8D7E00481A6239440059EC34 /* Prototope.xcodeproj */; 69 | proxyType = 1; 70 | remoteGlobalIDString = EBE3A75519DF79C400A77736; 71 | remoteInfo = Prototope; 72 | }; 73 | 9620D4FE1C5EF821001E2A23 /* PBXContainerItemProxy */ = { 74 | isa = PBXContainerItemProxy; 75 | containerPortal = 8D7E00481A6239440059EC34 /* Prototope.xcodeproj */; 76 | proxyType = 2; 77 | remoteGlobalIDString = 96A192331B531B3200D73B07; 78 | remoteInfo = PrototopeOSX; 79 | }; 80 | 9620D5001C5EF821001E2A23 /* PBXContainerItemProxy */ = { 81 | isa = PBXContainerItemProxy; 82 | containerPortal = 8D7E00481A6239440059EC34 /* Prototope.xcodeproj */; 83 | proxyType = 2; 84 | remoteGlobalIDString = 96A1923D1B531B3300D73B07; 85 | remoteInfo = PrototopeOSXTests; 86 | }; 87 | 9632F65A1A8D4C6A003D386B /* PBXContainerItemProxy */ = { 88 | isa = PBXContainerItemProxy; 89 | containerPortal = 8D7E00481A6239440059EC34 /* Prototope.xcodeproj */; 90 | proxyType = 2; 91 | remoteGlobalIDString = EB6BFB801A857326002B4E6A; 92 | remoteInfo = Protocaster; 93 | }; 94 | 9632F65C1A8D4C6A003D386B /* PBXContainerItemProxy */ = { 95 | isa = PBXContainerItemProxy; 96 | containerPortal = 8D7E00481A6239440059EC34 /* Prototope.xcodeproj */; 97 | proxyType = 2; 98 | remoteGlobalIDString = EB6BFB911A857326002B4E6A; 99 | remoteInfo = ProtocasterTests; 100 | }; 101 | 9632F65E1A8D4C6A003D386B /* PBXContainerItemProxy */ = { 102 | isa = PBXContainerItemProxy; 103 | containerPortal = 8D7E00481A6239440059EC34 /* Prototope.xcodeproj */; 104 | proxyType = 2; 105 | remoteGlobalIDString = EB6BFBB51A857920002B4E6A; 106 | remoteInfo = Protoscope; 107 | }; 108 | 9632F6601A8D4C6A003D386B /* PBXContainerItemProxy */ = { 109 | isa = PBXContainerItemProxy; 110 | containerPortal = 8D7E00481A6239440059EC34 /* Prototope.xcodeproj */; 111 | proxyType = 2; 112 | remoteGlobalIDString = EB6BFBC91A857921002B4E6A; 113 | remoteInfo = ProtoscopeTests; 114 | }; 115 | EBC00D9B1A859EF4008D4FF5 /* PBXContainerItemProxy */ = { 116 | isa = PBXContainerItemProxy; 117 | containerPortal = 8D7E00481A6239440059EC34 /* Prototope.xcodeproj */; 118 | proxyType = 2; 119 | remoteGlobalIDString = EB453D8F1A7AB0B2006F2A72; 120 | remoteInfo = PrototopeJSBridge; 121 | }; 122 | EBC00D9D1A859EF4008D4FF5 /* PBXContainerItemProxy */ = { 123 | isa = PBXContainerItemProxy; 124 | containerPortal = 8D7E00481A6239440059EC34 /* Prototope.xcodeproj */; 125 | proxyType = 2; 126 | remoteGlobalIDString = EB453D991A7AB0B2006F2A72; 127 | remoteInfo = PrototopeJSBridgeTests; 128 | }; 129 | /* End PBXContainerItemProxy section */ 130 | 131 | /* Begin PBXCopyFilesBuildPhase section */ 132 | 8D7E005E1A623F700059EC34 /* Copy Files */ = { 133 | isa = PBXCopyFilesBuildPhase; 134 | buildActionMask = 2147483647; 135 | dstPath = ""; 136 | dstSubfolderSpec = 10; 137 | files = ( 138 | 8D7E005F1A623F850059EC34 /* Prototope.framework in Copy Files */, 139 | ); 140 | name = "Copy Files"; 141 | runOnlyForDeploymentPostprocessing = 0; 142 | }; 143 | /* End PBXCopyFilesBuildPhase section */ 144 | 145 | /* Begin PBXFileReference section */ 146 | 6972003B1A80A631009AB8D7 /* TouchUnicorns.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchUnicorns.swift; sourceTree = ""; }; 147 | 697200571A8347F0009AB8D7 /* ThrowCats.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThrowCats.swift; sourceTree = ""; }; 148 | 756AEA601A7794280039E8AB /* RotationGesturePlaypen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RotationGesturePlaypen.swift; sourceTree = ""; }; 149 | 7843BABB1A89A0EF00DC203F /* ForcePull.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForcePull.swift; sourceTree = ""; }; 150 | 8D7E00231A6238A10059EC34 /* OhaiPrototope.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OhaiPrototope.app; sourceTree = BUILT_PRODUCTS_DIR; }; 151 | 8D7E00271A6238A10059EC34 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 152 | 8D7E00281A6238A10059EC34 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 153 | 8D7E002A1A6238A10059EC34 /* SceneViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneViewController.swift; sourceTree = ""; }; 154 | 8D7E002F1A6238A10059EC34 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 155 | 8D7E00321A6238A10059EC34 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 156 | 8D7E00381A6238A10059EC34 /* OhaiPrototopeTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OhaiPrototopeTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 157 | 8D7E003D1A6238A10059EC34 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 158 | 8D7E003E1A6238A10059EC34 /* OhaiPrototopeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OhaiPrototopeTests.swift; sourceTree = ""; }; 159 | 8D7E00481A6239440059EC34 /* Prototope.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Prototope.xcodeproj; path = prototope/Prototope.xcodeproj; sourceTree = ""; }; 160 | 8D7E005C1A623C140059EC34 /* TouchEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchEvents.swift; sourceTree = ""; }; 161 | 8DC95FA41A69E05000D18D30 /* ShadowLightsource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShadowLightsource.swift; sourceTree = ""; }; 162 | 8DEBACDE1A7326C5003CF491 /* TouchAnimators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchAnimators.swift; sourceTree = ""; }; 163 | 9619D3AD1A8E57780022DC54 /* LayoutScene.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayoutScene.swift; sourceTree = ""; }; 164 | 961AC1FB1A93B99A00ED9744 /* SpeechyScene.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpeechyScene.swift; sourceTree = ""; }; 165 | 9632F6501A8D4C6A003D386B /* ScrollScene.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollScene.swift; sourceTree = ""; }; 166 | 969933161A893E0F0036684E /* VideoScene.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoScene.swift; sourceTree = ""; }; 167 | 9699331F1A895E980036684E /* jeff.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = jeff.mp4; sourceTree = ""; }; 168 | CD11D8641A817DDA004F1197 /* TouchParticles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchParticles.swift; sourceTree = ""; }; 169 | CD4C5E291AC5BC64002288EB /* ShapeScene.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShapeScene.swift; sourceTree = ""; }; 170 | EB06C1011A853FC50095489F /* SceneListingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneListingViewController.swift; sourceTree = ""; }; 171 | EB06C1031A8540B80095489F /* SceneIndex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneIndex.swift; sourceTree = ""; }; 172 | EB190C201A66E85C00F970E1 /* TunableValues.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = TunableValues.json; sourceTree = ""; }; 173 | EB9557F91AE71A4D00ABCF9B /* TreeMaker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TreeMaker.swift; sourceTree = ""; }; 174 | /* End PBXFileReference section */ 175 | 176 | /* Begin PBXFrameworksBuildPhase section */ 177 | 8D7E00201A6238A10059EC34 /* Frameworks */ = { 178 | isa = PBXFrameworksBuildPhase; 179 | buildActionMask = 2147483647; 180 | files = ( 181 | 8D7E00571A623A3C0059EC34 /* Prototope.framework in Frameworks */, 182 | ); 183 | runOnlyForDeploymentPostprocessing = 0; 184 | }; 185 | 8D7E00351A6238A10059EC34 /* Frameworks */ = { 186 | isa = PBXFrameworksBuildPhase; 187 | buildActionMask = 2147483647; 188 | files = ( 189 | ); 190 | runOnlyForDeploymentPostprocessing = 0; 191 | }; 192 | /* End PBXFrameworksBuildPhase section */ 193 | 194 | /* Begin PBXGroup section */ 195 | 8D7E001A1A6238A10059EC34 = { 196 | isa = PBXGroup; 197 | children = ( 198 | 8D7E00481A6239440059EC34 /* Prototope.xcodeproj */, 199 | 8D7E00251A6238A10059EC34 /* OhaiPrototope */, 200 | 8D7E003B1A6238A10059EC34 /* OhaiPrototopeTests */, 201 | 8D7E00241A6238A10059EC34 /* Products */, 202 | ); 203 | sourceTree = ""; 204 | }; 205 | 8D7E00241A6238A10059EC34 /* Products */ = { 206 | isa = PBXGroup; 207 | children = ( 208 | 8D7E00231A6238A10059EC34 /* OhaiPrototope.app */, 209 | 8D7E00381A6238A10059EC34 /* OhaiPrototopeTests.xctest */, 210 | ); 211 | name = Products; 212 | sourceTree = ""; 213 | }; 214 | 8D7E00251A6238A10059EC34 /* OhaiPrototope */ = { 215 | isa = PBXGroup; 216 | children = ( 217 | 8DEBACDE1A7326C5003CF491 /* TouchAnimators.swift */, 218 | 6972003B1A80A631009AB8D7 /* TouchUnicorns.swift */, 219 | 8DC95FA41A69E05000D18D30 /* ShadowLightsource.swift */, 220 | 8D7E005C1A623C140059EC34 /* TouchEvents.swift */, 221 | EB06C1031A8540B80095489F /* SceneIndex.swift */, 222 | CD11D8641A817DDA004F1197 /* TouchParticles.swift */, 223 | 7843BABB1A89A0EF00DC203F /* ForcePull.swift */, 224 | EB06C1051A8540C00095489F /* Scenes */, 225 | 8D7E00261A6238A10059EC34 /* Supporting Files */, 226 | EB190C201A66E85C00F970E1 /* TunableValues.json */, 227 | EB06C10D1A854ADC0095489F /* App UI */, 228 | ); 229 | path = OhaiPrototope; 230 | sourceTree = ""; 231 | }; 232 | 8D7E00261A6238A10059EC34 /* Supporting Files */ = { 233 | isa = PBXGroup; 234 | children = ( 235 | 8D7E002F1A6238A10059EC34 /* Images.xcassets */, 236 | 8D7E00311A6238A10059EC34 /* LaunchScreen.xib */, 237 | 8D7E00271A6238A10059EC34 /* Info.plist */, 238 | 9699331F1A895E980036684E /* jeff.mp4 */, 239 | ); 240 | name = "Supporting Files"; 241 | sourceTree = ""; 242 | }; 243 | 8D7E003B1A6238A10059EC34 /* OhaiPrototopeTests */ = { 244 | isa = PBXGroup; 245 | children = ( 246 | 8D7E003E1A6238A10059EC34 /* OhaiPrototopeTests.swift */, 247 | 8D7E003C1A6238A10059EC34 /* Supporting Files */, 248 | ); 249 | path = OhaiPrototopeTests; 250 | sourceTree = ""; 251 | }; 252 | 8D7E003C1A6238A10059EC34 /* Supporting Files */ = { 253 | isa = PBXGroup; 254 | children = ( 255 | 8D7E003D1A6238A10059EC34 /* Info.plist */, 256 | ); 257 | name = "Supporting Files"; 258 | sourceTree = ""; 259 | }; 260 | 8D7E00491A6239440059EC34 /* Products */ = { 261 | isa = PBXGroup; 262 | children = ( 263 | 8D7E00501A6239440059EC34 /* Prototope.framework */, 264 | 8D7E00521A6239440059EC34 /* PrototopeTestApp.app */, 265 | 8D7E00541A6239440059EC34 /* PrototopeTests.xctest */, 266 | EBC00D9C1A859EF4008D4FF5 /* PrototopeJSBridge.framework */, 267 | EBC00D9E1A859EF4008D4FF5 /* PrototopeJSBridgeTests.xctest */, 268 | 9632F65B1A8D4C6A003D386B /* Protocaster.app */, 269 | 9632F65D1A8D4C6A003D386B /* ProtocasterTests.xctest */, 270 | 9632F65F1A8D4C6A003D386B /* Protoscope.app */, 271 | 9632F6611A8D4C6A003D386B /* ProtoscopeTests.xctest */, 272 | 9620D4FF1C5EF821001E2A23 /* PrototopeOSX.framework */, 273 | 9620D5011C5EF821001E2A23 /* PrototopeOSXTests.xctest */, 274 | ); 275 | name = Products; 276 | sourceTree = ""; 277 | }; 278 | EB06C1051A8540C00095489F /* Scenes */ = { 279 | isa = PBXGroup; 280 | children = ( 281 | 697200571A8347F0009AB8D7 /* ThrowCats.swift */, 282 | 756AEA601A7794280039E8AB /* RotationGesturePlaypen.swift */, 283 | 969933161A893E0F0036684E /* VideoScene.swift */, 284 | 9632F6501A8D4C6A003D386B /* ScrollScene.swift */, 285 | 9619D3AD1A8E57780022DC54 /* LayoutScene.swift */, 286 | 961AC1FB1A93B99A00ED9744 /* SpeechyScene.swift */, 287 | EB9557F91AE71A4D00ABCF9B /* TreeMaker.swift */, 288 | CD4C5E291AC5BC64002288EB /* ShapeScene.swift */, 289 | ); 290 | name = Scenes; 291 | sourceTree = ""; 292 | }; 293 | EB06C10D1A854ADC0095489F /* App UI */ = { 294 | isa = PBXGroup; 295 | children = ( 296 | 8D7E00281A6238A10059EC34 /* AppDelegate.swift */, 297 | 8D7E002A1A6238A10059EC34 /* SceneViewController.swift */, 298 | EB06C1011A853FC50095489F /* SceneListingViewController.swift */, 299 | ); 300 | name = "App UI"; 301 | sourceTree = ""; 302 | }; 303 | /* End PBXGroup section */ 304 | 305 | /* Begin PBXNativeTarget section */ 306 | 8D7E00221A6238A10059EC34 /* OhaiPrototope */ = { 307 | isa = PBXNativeTarget; 308 | buildConfigurationList = 8D7E00421A6238A10059EC34 /* Build configuration list for PBXNativeTarget "OhaiPrototope" */; 309 | buildPhases = ( 310 | 8D7E001F1A6238A10059EC34 /* Sources */, 311 | 8D7E00201A6238A10059EC34 /* Frameworks */, 312 | 8D7E00211A6238A10059EC34 /* Resources */, 313 | 8D7E005E1A623F700059EC34 /* Copy Files */, 314 | ); 315 | buildRules = ( 316 | ); 317 | dependencies = ( 318 | 8D7E005A1A623A3C0059EC34 /* PBXTargetDependency */, 319 | ); 320 | name = OhaiPrototope; 321 | productName = OhaiPrototope; 322 | productReference = 8D7E00231A6238A10059EC34 /* OhaiPrototope.app */; 323 | productType = "com.apple.product-type.application"; 324 | }; 325 | 8D7E00371A6238A10059EC34 /* OhaiPrototopeTests */ = { 326 | isa = PBXNativeTarget; 327 | buildConfigurationList = 8D7E00451A6238A10059EC34 /* Build configuration list for PBXNativeTarget "OhaiPrototopeTests" */; 328 | buildPhases = ( 329 | 8D7E00341A6238A10059EC34 /* Sources */, 330 | 8D7E00351A6238A10059EC34 /* Frameworks */, 331 | 8D7E00361A6238A10059EC34 /* Resources */, 332 | ); 333 | buildRules = ( 334 | ); 335 | dependencies = ( 336 | 8D7E003A1A6238A10059EC34 /* PBXTargetDependency */, 337 | ); 338 | name = OhaiPrototopeTests; 339 | productName = OhaiPrototopeTests; 340 | productReference = 8D7E00381A6238A10059EC34 /* OhaiPrototopeTests.xctest */; 341 | productType = "com.apple.product-type.bundle.unit-test"; 342 | }; 343 | /* End PBXNativeTarget section */ 344 | 345 | /* Begin PBXProject section */ 346 | 8D7E001B1A6238A10059EC34 /* Project object */ = { 347 | isa = PBXProject; 348 | attributes = { 349 | LastSwiftMigration = 0720; 350 | LastSwiftUpdateCheck = 0720; 351 | LastUpgradeCheck = 0610; 352 | ORGANIZATIONNAME = "Prototope Research Facility"; 353 | TargetAttributes = { 354 | 8D7E00221A6238A10059EC34 = { 355 | CreatedOnToolsVersion = 6.1.1; 356 | DevelopmentTeam = FMPA3MXEW7; 357 | }; 358 | 8D7E00371A6238A10059EC34 = { 359 | CreatedOnToolsVersion = 6.1.1; 360 | TestTargetID = 8D7E00221A6238A10059EC34; 361 | }; 362 | }; 363 | }; 364 | buildConfigurationList = 8D7E001E1A6238A10059EC34 /* Build configuration list for PBXProject "OhaiPrototope" */; 365 | compatibilityVersion = "Xcode 3.2"; 366 | developmentRegion = English; 367 | hasScannedForEncodings = 0; 368 | knownRegions = ( 369 | en, 370 | Base, 371 | ); 372 | mainGroup = 8D7E001A1A6238A10059EC34; 373 | productRefGroup = 8D7E00241A6238A10059EC34 /* Products */; 374 | projectDirPath = ""; 375 | projectReferences = ( 376 | { 377 | ProductGroup = 8D7E00491A6239440059EC34 /* Products */; 378 | ProjectRef = 8D7E00481A6239440059EC34 /* Prototope.xcodeproj */; 379 | }, 380 | ); 381 | projectRoot = ""; 382 | targets = ( 383 | 8D7E00221A6238A10059EC34 /* OhaiPrototope */, 384 | 8D7E00371A6238A10059EC34 /* OhaiPrototopeTests */, 385 | ); 386 | }; 387 | /* End PBXProject section */ 388 | 389 | /* Begin PBXReferenceProxy section */ 390 | 8D7E00501A6239440059EC34 /* Prototope.framework */ = { 391 | isa = PBXReferenceProxy; 392 | fileType = wrapper.framework; 393 | path = Prototope.framework; 394 | remoteRef = 8D7E004F1A6239440059EC34 /* PBXContainerItemProxy */; 395 | sourceTree = BUILT_PRODUCTS_DIR; 396 | }; 397 | 8D7E00521A6239440059EC34 /* PrototopeTestApp.app */ = { 398 | isa = PBXReferenceProxy; 399 | fileType = wrapper.application; 400 | path = PrototopeTestApp.app; 401 | remoteRef = 8D7E00511A6239440059EC34 /* PBXContainerItemProxy */; 402 | sourceTree = BUILT_PRODUCTS_DIR; 403 | }; 404 | 8D7E00541A6239440059EC34 /* PrototopeTests.xctest */ = { 405 | isa = PBXReferenceProxy; 406 | fileType = wrapper.cfbundle; 407 | path = PrototopeTests.xctest; 408 | remoteRef = 8D7E00531A6239440059EC34 /* PBXContainerItemProxy */; 409 | sourceTree = BUILT_PRODUCTS_DIR; 410 | }; 411 | 9620D4FF1C5EF821001E2A23 /* PrototopeOSX.framework */ = { 412 | isa = PBXReferenceProxy; 413 | fileType = wrapper.framework; 414 | path = PrototopeOSX.framework; 415 | remoteRef = 9620D4FE1C5EF821001E2A23 /* PBXContainerItemProxy */; 416 | sourceTree = BUILT_PRODUCTS_DIR; 417 | }; 418 | 9620D5011C5EF821001E2A23 /* PrototopeOSXTests.xctest */ = { 419 | isa = PBXReferenceProxy; 420 | fileType = wrapper.cfbundle; 421 | path = PrototopeOSXTests.xctest; 422 | remoteRef = 9620D5001C5EF821001E2A23 /* PBXContainerItemProxy */; 423 | sourceTree = BUILT_PRODUCTS_DIR; 424 | }; 425 | 9632F65B1A8D4C6A003D386B /* Protocaster.app */ = { 426 | isa = PBXReferenceProxy; 427 | fileType = wrapper.application; 428 | path = Protocaster.app; 429 | remoteRef = 9632F65A1A8D4C6A003D386B /* PBXContainerItemProxy */; 430 | sourceTree = BUILT_PRODUCTS_DIR; 431 | }; 432 | 9632F65D1A8D4C6A003D386B /* ProtocasterTests.xctest */ = { 433 | isa = PBXReferenceProxy; 434 | fileType = wrapper.cfbundle; 435 | path = ProtocasterTests.xctest; 436 | remoteRef = 9632F65C1A8D4C6A003D386B /* PBXContainerItemProxy */; 437 | sourceTree = BUILT_PRODUCTS_DIR; 438 | }; 439 | 9632F65F1A8D4C6A003D386B /* Protoscope.app */ = { 440 | isa = PBXReferenceProxy; 441 | fileType = wrapper.application; 442 | path = Protoscope.app; 443 | remoteRef = 9632F65E1A8D4C6A003D386B /* PBXContainerItemProxy */; 444 | sourceTree = BUILT_PRODUCTS_DIR; 445 | }; 446 | 9632F6611A8D4C6A003D386B /* ProtoscopeTests.xctest */ = { 447 | isa = PBXReferenceProxy; 448 | fileType = wrapper.cfbundle; 449 | path = ProtoscopeTests.xctest; 450 | remoteRef = 9632F6601A8D4C6A003D386B /* PBXContainerItemProxy */; 451 | sourceTree = BUILT_PRODUCTS_DIR; 452 | }; 453 | EBC00D9C1A859EF4008D4FF5 /* PrototopeJSBridge.framework */ = { 454 | isa = PBXReferenceProxy; 455 | fileType = wrapper.framework; 456 | path = PrototopeJSBridge.framework; 457 | remoteRef = EBC00D9B1A859EF4008D4FF5 /* PBXContainerItemProxy */; 458 | sourceTree = BUILT_PRODUCTS_DIR; 459 | }; 460 | EBC00D9E1A859EF4008D4FF5 /* PrototopeJSBridgeTests.xctest */ = { 461 | isa = PBXReferenceProxy; 462 | fileType = wrapper.cfbundle; 463 | path = PrototopeJSBridgeTests.xctest; 464 | remoteRef = EBC00D9D1A859EF4008D4FF5 /* PBXContainerItemProxy */; 465 | sourceTree = BUILT_PRODUCTS_DIR; 466 | }; 467 | /* End PBXReferenceProxy section */ 468 | 469 | /* Begin PBXResourcesBuildPhase section */ 470 | 8D7E00211A6238A10059EC34 /* Resources */ = { 471 | isa = PBXResourcesBuildPhase; 472 | buildActionMask = 2147483647; 473 | files = ( 474 | EB190C211A66E85C00F970E1 /* TunableValues.json in Resources */, 475 | 8D7E00331A6238A10059EC34 /* LaunchScreen.xib in Resources */, 476 | 969933201A895F3E0036684E /* jeff.mp4 in Resources */, 477 | 8D7E00301A6238A10059EC34 /* Images.xcassets in Resources */, 478 | ); 479 | runOnlyForDeploymentPostprocessing = 0; 480 | }; 481 | 8D7E00361A6238A10059EC34 /* Resources */ = { 482 | isa = PBXResourcesBuildPhase; 483 | buildActionMask = 2147483647; 484 | files = ( 485 | ); 486 | runOnlyForDeploymentPostprocessing = 0; 487 | }; 488 | /* End PBXResourcesBuildPhase section */ 489 | 490 | /* Begin PBXSourcesBuildPhase section */ 491 | 8D7E001F1A6238A10059EC34 /* Sources */ = { 492 | isa = PBXSourcesBuildPhase; 493 | buildActionMask = 2147483647; 494 | files = ( 495 | 8D7E005D1A623C140059EC34 /* TouchEvents.swift in Sources */, 496 | CD11D8651A817DDA004F1197 /* TouchParticles.swift in Sources */, 497 | 756AEA611A7794280039E8AB /* RotationGesturePlaypen.swift in Sources */, 498 | 8DC95FA51A69E05000D18D30 /* ShadowLightsource.swift in Sources */, 499 | 969933171A893E0F0036684E /* VideoScene.swift in Sources */, 500 | EB06C1021A853FC50095489F /* SceneListingViewController.swift in Sources */, 501 | 697200581A8347F0009AB8D7 /* ThrowCats.swift in Sources */, 502 | EB06C1041A8540B80095489F /* SceneIndex.swift in Sources */, 503 | 8D7E002B1A6238A10059EC34 /* SceneViewController.swift in Sources */, 504 | EB9557FA1AE71A4D00ABCF9B /* TreeMaker.swift in Sources */, 505 | 8DEBACDF1A7326C5003CF491 /* TouchAnimators.swift in Sources */, 506 | 7843BABC1A89A0EF00DC203F /* ForcePull.swift in Sources */, 507 | 6972003C1A80A631009AB8D7 /* TouchUnicorns.swift in Sources */, 508 | 961AC1FC1A93B99A00ED9744 /* SpeechyScene.swift in Sources */, 509 | 8D7E00291A6238A10059EC34 /* AppDelegate.swift in Sources */, 510 | CD4C5E2A1AC5BC64002288EB /* ShapeScene.swift in Sources */, 511 | 9619D3AE1A8E57780022DC54 /* LayoutScene.swift in Sources */, 512 | 9632F6511A8D4C6A003D386B /* ScrollScene.swift in Sources */, 513 | ); 514 | runOnlyForDeploymentPostprocessing = 0; 515 | }; 516 | 8D7E00341A6238A10059EC34 /* Sources */ = { 517 | isa = PBXSourcesBuildPhase; 518 | buildActionMask = 2147483647; 519 | files = ( 520 | 8D7E003F1A6238A10059EC34 /* OhaiPrototopeTests.swift in Sources */, 521 | ); 522 | runOnlyForDeploymentPostprocessing = 0; 523 | }; 524 | /* End PBXSourcesBuildPhase section */ 525 | 526 | /* Begin PBXTargetDependency section */ 527 | 8D7E003A1A6238A10059EC34 /* PBXTargetDependency */ = { 528 | isa = PBXTargetDependency; 529 | target = 8D7E00221A6238A10059EC34 /* OhaiPrototope */; 530 | targetProxy = 8D7E00391A6238A10059EC34 /* PBXContainerItemProxy */; 531 | }; 532 | 8D7E005A1A623A3C0059EC34 /* PBXTargetDependency */ = { 533 | isa = PBXTargetDependency; 534 | name = Prototope; 535 | targetProxy = 8D7E00591A623A3C0059EC34 /* PBXContainerItemProxy */; 536 | }; 537 | /* End PBXTargetDependency section */ 538 | 539 | /* Begin PBXVariantGroup section */ 540 | 8D7E00311A6238A10059EC34 /* LaunchScreen.xib */ = { 541 | isa = PBXVariantGroup; 542 | children = ( 543 | 8D7E00321A6238A10059EC34 /* Base */, 544 | ); 545 | name = LaunchScreen.xib; 546 | sourceTree = ""; 547 | }; 548 | /* End PBXVariantGroup section */ 549 | 550 | /* Begin XCBuildConfiguration section */ 551 | 8D7E00401A6238A10059EC34 /* Debug */ = { 552 | isa = XCBuildConfiguration; 553 | buildSettings = { 554 | ALWAYS_SEARCH_USER_PATHS = NO; 555 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 556 | CLANG_CXX_LIBRARY = "libc++"; 557 | CLANG_ENABLE_MODULES = YES; 558 | CLANG_ENABLE_OBJC_ARC = YES; 559 | CLANG_WARN_BOOL_CONVERSION = YES; 560 | CLANG_WARN_CONSTANT_CONVERSION = YES; 561 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 562 | CLANG_WARN_EMPTY_BODY = YES; 563 | CLANG_WARN_ENUM_CONVERSION = YES; 564 | CLANG_WARN_INT_CONVERSION = YES; 565 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 566 | CLANG_WARN_UNREACHABLE_CODE = YES; 567 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 568 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 569 | COPY_PHASE_STRIP = NO; 570 | ENABLE_STRICT_OBJC_MSGSEND = YES; 571 | GCC_C_LANGUAGE_STANDARD = gnu99; 572 | GCC_DYNAMIC_NO_PIC = NO; 573 | GCC_OPTIMIZATION_LEVEL = 0; 574 | GCC_PREPROCESSOR_DEFINITIONS = ( 575 | "DEBUG=1", 576 | "$(inherited)", 577 | ); 578 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 579 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 580 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 581 | GCC_WARN_UNDECLARED_SELECTOR = YES; 582 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 583 | GCC_WARN_UNUSED_FUNCTION = YES; 584 | GCC_WARN_UNUSED_VARIABLE = YES; 585 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 586 | MTL_ENABLE_DEBUG_INFO = YES; 587 | ONLY_ACTIVE_ARCH = YES; 588 | SDKROOT = iphoneos; 589 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 590 | TARGETED_DEVICE_FAMILY = 2; 591 | }; 592 | name = Debug; 593 | }; 594 | 8D7E00411A6238A10059EC34 /* Release */ = { 595 | isa = XCBuildConfiguration; 596 | buildSettings = { 597 | ALWAYS_SEARCH_USER_PATHS = NO; 598 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 599 | CLANG_CXX_LIBRARY = "libc++"; 600 | CLANG_ENABLE_MODULES = YES; 601 | CLANG_ENABLE_OBJC_ARC = YES; 602 | CLANG_WARN_BOOL_CONVERSION = YES; 603 | CLANG_WARN_CONSTANT_CONVERSION = YES; 604 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 605 | CLANG_WARN_EMPTY_BODY = YES; 606 | CLANG_WARN_ENUM_CONVERSION = YES; 607 | CLANG_WARN_INT_CONVERSION = YES; 608 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 609 | CLANG_WARN_UNREACHABLE_CODE = YES; 610 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 611 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 612 | COPY_PHASE_STRIP = YES; 613 | ENABLE_NS_ASSERTIONS = NO; 614 | ENABLE_STRICT_OBJC_MSGSEND = YES; 615 | GCC_C_LANGUAGE_STANDARD = gnu99; 616 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 617 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 618 | GCC_WARN_UNDECLARED_SELECTOR = YES; 619 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 620 | GCC_WARN_UNUSED_FUNCTION = YES; 621 | GCC_WARN_UNUSED_VARIABLE = YES; 622 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 623 | MTL_ENABLE_DEBUG_INFO = NO; 624 | SDKROOT = iphoneos; 625 | TARGETED_DEVICE_FAMILY = 2; 626 | VALIDATE_PRODUCT = YES; 627 | }; 628 | name = Release; 629 | }; 630 | 8D7E00431A6238A10059EC34 /* Debug */ = { 631 | isa = XCBuildConfiguration; 632 | buildSettings = { 633 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 634 | CLANG_ENABLE_MODULES = YES; 635 | CODE_SIGN_IDENTITY = "iPhone Developer"; 636 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 637 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 638 | HEADER_SEARCH_PATHS = ( 639 | "$(inherited)", 640 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 641 | "$(SRCROOT)/prototope/Prototope", 642 | "$(SRCROOT)/prototope/ThirdParty/**", 643 | ); 644 | INFOPLIST_FILE = OhaiPrototope/Info.plist; 645 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 646 | LIBRARY_SEARCH_PATHS = "$(SRCROOT)/prototope/ThirdParty/**"; 647 | OTHER_LDFLAGS = ( 648 | "-ObjC", 649 | "-lc++", 650 | ); 651 | PRODUCT_NAME = "$(TARGET_NAME)"; 652 | PROVISIONING_PROFILE = ""; 653 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 654 | }; 655 | name = Debug; 656 | }; 657 | 8D7E00441A6238A10059EC34 /* Release */ = { 658 | isa = XCBuildConfiguration; 659 | buildSettings = { 660 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 661 | CLANG_ENABLE_MODULES = YES; 662 | CODE_SIGN_IDENTITY = "iPhone Developer"; 663 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 664 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 665 | HEADER_SEARCH_PATHS = ( 666 | "$(inherited)", 667 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 668 | "$(SRCROOT)/prototope/Prototope", 669 | "$(SRCROOT)/prototope/ThirdParty/**", 670 | ); 671 | INFOPLIST_FILE = OhaiPrototope/Info.plist; 672 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 673 | LIBRARY_SEARCH_PATHS = "$(SRCROOT)/prototope/ThirdParty/**"; 674 | OTHER_LDFLAGS = ( 675 | "-ObjC", 676 | "-lc++", 677 | ); 678 | PRODUCT_NAME = "$(TARGET_NAME)"; 679 | PROVISIONING_PROFILE = ""; 680 | }; 681 | name = Release; 682 | }; 683 | 8D7E00461A6238A10059EC34 /* Debug */ = { 684 | isa = XCBuildConfiguration; 685 | buildSettings = { 686 | BUNDLE_LOADER = "$(TEST_HOST)"; 687 | FRAMEWORK_SEARCH_PATHS = ( 688 | "$(SDKROOT)/Developer/Library/Frameworks", 689 | "$(inherited)", 690 | ); 691 | GCC_PREPROCESSOR_DEFINITIONS = ( 692 | "DEBUG=1", 693 | "$(inherited)", 694 | ); 695 | INFOPLIST_FILE = OhaiPrototopeTests/Info.plist; 696 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 697 | PRODUCT_NAME = "$(TARGET_NAME)"; 698 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/OhaiPrototope.app/OhaiPrototope"; 699 | }; 700 | name = Debug; 701 | }; 702 | 8D7E00471A6238A10059EC34 /* Release */ = { 703 | isa = XCBuildConfiguration; 704 | buildSettings = { 705 | BUNDLE_LOADER = "$(TEST_HOST)"; 706 | FRAMEWORK_SEARCH_PATHS = ( 707 | "$(SDKROOT)/Developer/Library/Frameworks", 708 | "$(inherited)", 709 | ); 710 | INFOPLIST_FILE = OhaiPrototopeTests/Info.plist; 711 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 712 | PRODUCT_NAME = "$(TARGET_NAME)"; 713 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/OhaiPrototope.app/OhaiPrototope"; 714 | }; 715 | name = Release; 716 | }; 717 | /* End XCBuildConfiguration section */ 718 | 719 | /* Begin XCConfigurationList section */ 720 | 8D7E001E1A6238A10059EC34 /* Build configuration list for PBXProject "OhaiPrototope" */ = { 721 | isa = XCConfigurationList; 722 | buildConfigurations = ( 723 | 8D7E00401A6238A10059EC34 /* Debug */, 724 | 8D7E00411A6238A10059EC34 /* Release */, 725 | ); 726 | defaultConfigurationIsVisible = 0; 727 | defaultConfigurationName = Release; 728 | }; 729 | 8D7E00421A6238A10059EC34 /* Build configuration list for PBXNativeTarget "OhaiPrototope" */ = { 730 | isa = XCConfigurationList; 731 | buildConfigurations = ( 732 | 8D7E00431A6238A10059EC34 /* Debug */, 733 | 8D7E00441A6238A10059EC34 /* Release */, 734 | ); 735 | defaultConfigurationIsVisible = 0; 736 | defaultConfigurationName = Release; 737 | }; 738 | 8D7E00451A6238A10059EC34 /* Build configuration list for PBXNativeTarget "OhaiPrototopeTests" */ = { 739 | isa = XCConfigurationList; 740 | buildConfigurations = ( 741 | 8D7E00461A6238A10059EC34 /* Debug */, 742 | 8D7E00471A6238A10059EC34 /* Release */, 743 | ); 744 | defaultConfigurationIsVisible = 0; 745 | defaultConfigurationName = Release; 746 | }; 747 | /* End XCConfigurationList section */ 748 | }; 749 | rootObject = 8D7E001B1A6238A10059EC34 /* Project object */; 750 | } 751 | -------------------------------------------------------------------------------- /OhaiPrototope.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /OhaiPrototope.xcodeproj/xcshareddata/xcschemes/OhaiPrototope.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 75 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 94 | 100 | 101 | 102 | 103 | 105 | 106 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /OhaiPrototope/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // OhaiPrototope 4 | // 5 | // Created by Marcos Ojeda on 1/10/15. 6 | // Copyright (c) 2015 Marcos Ojeda. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow! 14 | var navigationController: UINavigationController! 15 | var sceneListingViewController: SceneListingViewController! 16 | 17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 18 | window = UIWindow(frame: UIScreen.mainScreen().bounds) 19 | window.makeKeyAndVisible() 20 | 21 | sceneListingViewController = SceneListingViewController( 22 | scenes: Scene.sceneIndex, 23 | sceneActivationHandler: { [unowned self] in self.activateScene($0) } 24 | ) 25 | sceneListingViewController.title = "Prototypes" 26 | navigationController = UINavigationController(rootViewController: sceneListingViewController) 27 | navigationController.interactivePopGestureRecognizer?.enabled = false 28 | window.rootViewController = navigationController 29 | 30 | let swipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "handleSwipeBackGesture:") 31 | swipeGestureRecognizer.numberOfTouchesRequired = 3 32 | swipeGestureRecognizer.direction = .Right 33 | window.addGestureRecognizer(swipeGestureRecognizer) 34 | return true 35 | } 36 | 37 | private func activateScene(scene: Scene) { 38 | let sceneViewController = SceneViewController(scene: scene) 39 | sceneViewController.backActionHandler = { self.navigateToList() } 40 | 41 | navigationController.setNavigationBarHidden(true, animated: true) 42 | navigationController.pushViewController(sceneViewController, animated: true) 43 | 44 | if !NSUserDefaults.standardUserDefaults().boolForKey("hasSeenNavigationWarning") { 45 | UIAlertView(title: "Navigation Tutorial", message: "Press the 'escape' key or swipe back with three fingers to navigate back.\n\nYou won't see this message again.", delegate: nil, cancelButtonTitle: "OK").show() 46 | NSUserDefaults.standardUserDefaults().setBool(true, forKey:"hasSeenNavigationWarning") 47 | } 48 | } 49 | 50 | func navigateToList() { 51 | self.navigationController.setNavigationBarHidden(false, animated: true) 52 | self.navigationController.setViewControllers([sceneListingViewController], animated: true) 53 | } 54 | 55 | func handleSwipeBackGesture(gesture: UIGestureRecognizer!) { 56 | navigateToList() 57 | } 58 | 59 | 60 | } 61 | 62 | -------------------------------------------------------------------------------- /OhaiPrototope/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /OhaiPrototope/ForcePull.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainScene.swift 3 | // OhaiPrototope 4 | // 5 | // Multi-touch demo where each touch creates a gravitational pull. Shows how 6 | // to create a simple physics simulation by using Heartbeat to recalculate 7 | // physical properties on every frame. You can also directly drag and throw 8 | // the circle. 9 | // 10 | // Created by Nefaur Khandker on 1/27/15. 11 | // Copyright (c) 2015 Khan Academy. All rights reserved. 12 | // 13 | 14 | import Prototope 15 | 16 | // Add another name for the Point class for the sake of semantics. 17 | typealias Vector = Point 18 | 19 | extension Vector { 20 | 21 | func normalized() -> Vector { 22 | let magnitude = self.length 23 | if (magnitude != 0) { 24 | return self / magnitude 25 | } else { 26 | return self 27 | } 28 | } 29 | 30 | } 31 | 32 | // Same as a layer, but it now has mass, velocity, etc. 33 | public class DynamicLayer: Layer { 34 | 35 | var mass: Double 36 | var velocity: Vector 37 | var forces: [String: Vector] // The forces that will be applied to this object on every frame 38 | var netForce: Vector { // Read-only. Computed from "forces". 39 | var force = Vector() 40 | for (key, f) in self.forces { 41 | force += f 42 | } 43 | return force 44 | } 45 | 46 | // Bookkeeping 47 | private var lastTimestamp: Timestamp 48 | private var renderer: Heartbeat? 49 | private var impulse: Vector // The force that will be applied to this object on just the next frame 50 | 51 | init(parent: Layer? = nil, name: String? = nil) { 52 | mass = 1 53 | velocity = Vector() 54 | forces = Dictionary() 55 | impulse = Vector() 56 | lastTimestamp = Timestamp.currentTimestamp 57 | 58 | super.init(parent: parent, name: name) 59 | 60 | renderer = Heartbeat(handler: { heartbeat in 61 | let currentTimestamp = heartbeat.timestamp 62 | let dt = currentTimestamp - self.lastTimestamp 63 | self.lastTimestamp = currentTimestamp 64 | 65 | let acceleration = self.mass * (self.netForce + self.impulse) 66 | self.velocity += dt * acceleration 67 | self.position += dt * self.velocity 68 | 69 | self.impulse = Vector() 70 | }) 71 | } 72 | 73 | func stop() { 74 | self.impulse = Vector() 75 | self.forces.removeAll() 76 | self.velocity = Vector() 77 | } 78 | 79 | func applyForce(id: String, force: Vector) { 80 | self.forces[id] = force 81 | } 82 | 83 | func removeForce(id: String) { 84 | self.forces.removeValueForKey(id) 85 | } 86 | 87 | func applyImpulse(impulse: Vector) { 88 | self.impulse += impulse 89 | } 90 | 91 | } 92 | 93 | // Generic behavior class applied to a layer that can be turned on/off 94 | class Behavior { 95 | let layer: Layer 96 | var active: Bool 97 | 98 | init(_ layer: Layer) { 99 | self.layer = layer 100 | self.active = true 101 | } 102 | } 103 | 104 | // Gravity + friction (OK, I probably should've separated these two out) 105 | class GravityBehavior: Behavior { 106 | 107 | let dynamicLayer: DynamicLayer 108 | let id: String 109 | var position: Point 110 | 111 | init(_ layer: DynamicLayer, id: String, position: Point = Point()) { 112 | self.dynamicLayer = layer 113 | self.id = id 114 | self.position = position 115 | super.init(layer) 116 | 117 | Heartbeat(handler: { heartbeat in 118 | if self.active { 119 | let targetPosition = self.position 120 | let position = self.layer.position 121 | let g = tunable(100, name: "gravity", min: 0, max: 10000) 122 | let f = tunable(10, name: "friction", min: 0, max: 10000) 123 | 124 | let distance = position.distanceToPoint(targetPosition) 125 | let gravity = g * (targetPosition - position) 126 | let friction = -f * self.dynamicLayer.velocity 127 | let netForce = gravity + friction 128 | self.dynamicLayer.applyForce(id, force: netForce) 129 | } else { 130 | self.dynamicLayer.removeForce(id) 131 | } 132 | }) 133 | } 134 | 135 | } 136 | 137 | class AttractionBehavior: Behavior { 138 | 139 | let attractedLayer: DynamicLayer 140 | let ambientGravity: GravityBehavior 141 | var dragTouch: UITouchID? // The touch (if any) that's dragging the layer 142 | var touches: [UITouchID: TouchSequence] // TouchSequence IDs to TouchSequence objects 143 | var gravityFields: [UITouchID: GravityBehavior] 144 | 145 | init(_ layer: Layer, attractedLayer: DynamicLayer, ambientGravity: GravityBehavior) { 146 | self.attractedLayer = attractedLayer 147 | self.ambientGravity = ambientGravity 148 | self.dragTouch = nil 149 | self.touches = Dictionary() 150 | self.gravityFields = Dictionary() 151 | super.init(layer) 152 | 153 | self.layer.touchBeganHandler = { centroidSequence in 154 | let id = centroidSequence.id 155 | let stringID: String = id.description 156 | let position = centroidSequence.currentSample.locationInLayer(self.layer) 157 | 158 | self.touches[id] = centroidSequence 159 | self.gravityFields[id] = GravityBehavior(self.attractedLayer, id: stringID, position: position) 160 | if self.dragTouch == nil { 161 | if attractedLayer.frame.contains(position) { 162 | self.dragTouch = id 163 | } 164 | } 165 | 166 | self.update() 167 | } 168 | self.layer.touchMovedHandler = { centroidSequence in 169 | let id = centroidSequence.id 170 | 171 | self.touches[id] = centroidSequence 172 | 173 | if id == self.dragTouch { 174 | // Repositions instead of applying a force (since we're dragging) 175 | let currentPoint = centroidSequence.currentSample.locationInLayer(self.layer) 176 | var previousPoint = currentPoint 177 | if let previousSample = centroidSequence.previousSample { 178 | previousPoint = previousSample.globalLocation 179 | } 180 | self.attractedLayer.position += currentPoint - previousPoint 181 | } 182 | 183 | self.update() 184 | } 185 | self.layer.touchEndedHandler = { centroidSequence in 186 | let id = centroidSequence.id 187 | 188 | if let gravity = self.gravityFields[id] { 189 | gravity.active = false 190 | self.gravityFields.removeValueForKey(id) 191 | } 192 | self.touches.removeValueForKey(id) 193 | 194 | if id == self.dragTouch { 195 | // Throws by applying an initial velocity 196 | var velocity = centroidSequence.currentVelocityInLayer(self.layer) 197 | self.attractedLayer.velocity = velocity 198 | 199 | self.dragTouch = nil 200 | } 201 | 202 | self.update() 203 | } 204 | } 205 | 206 | // Updates the forces that are applied based on which touches are where 207 | func update() { 208 | if let d = self.dragTouch { 209 | self.ambientGravity.active = false 210 | for (id, touchSequence) in self.touches { 211 | if let gravityBehavior = self.gravityFields[id] { 212 | gravityBehavior.active = false 213 | } 214 | } 215 | } else { 216 | if self.touches.count == 0 { 217 | self.ambientGravity.active = true 218 | } else { 219 | self.ambientGravity.active = false 220 | for (id, touchSequence) in self.touches { 221 | if let gravityBehavior = self.gravityFields[id] { 222 | let position = touchSequence.currentSample.locationInLayer(self.layer) 223 | gravityBehavior.active = true 224 | gravityBehavior.position = position 225 | } 226 | } 227 | } 228 | } 229 | } 230 | } 231 | 232 | class ForcePull { 233 | 234 | var bg: Layer! 235 | var circle: DynamicLayer! 236 | let ambientGravity: GravityBehavior 237 | let attractionBehavior: AttractionBehavior 238 | 239 | init() { 240 | bg = Layer(parent: Layer.root) 241 | bg.backgroundColor = Color(hex: 0xffffff) 242 | bg.frame = Layer.root.bounds 243 | 244 | circle = DynamicLayer(parent: bg) 245 | circle.backgroundColor = Color(hex: 0xff0088) 246 | circle.x = 0.5 * bg.width 247 | circle.y = 0.5 * bg.height 248 | 249 | self.ambientGravity = GravityBehavior(circle, id: "ambient", position: bg.bounds.center) 250 | self.attractionBehavior = AttractionBehavior(bg, attractedLayer: circle, ambientGravity: ambientGravity) 251 | 252 | tunable(100, name: "size", min: 44, max: 512) { size in 253 | self.circle.width = size 254 | self.circle.height = size 255 | self.circle.cornerRadius = 0.5 * size 256 | self.circle.mass = size / 100 257 | } 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /OhaiPrototope/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "ipad", 5 | "size" : "29x29", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "ipad", 10 | "size" : "29x29", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "ipad", 15 | "size" : "40x40", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "ipad", 20 | "size" : "40x40", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "ipad", 25 | "size" : "76x76", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "ipad", 30 | "size" : "76x76", 31 | "scale" : "2x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /OhaiPrototope/Images.xcassets/cat.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "cat.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OhaiPrototope/Images.xcassets/cat.imageset/cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Khan/OhaiPrototope/1112a442c7ff94f2d7d5e8c2bd2e8f28ceaf4600/OhaiPrototope/Images.xcassets/cat.imageset/cat.png -------------------------------------------------------------------------------- /OhaiPrototope/Images.xcassets/cloud.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "cloud.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "cloud@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /OhaiPrototope/Images.xcassets/cloud.imageset/cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Khan/OhaiPrototope/1112a442c7ff94f2d7d5e8c2bd2e8f28ceaf4600/OhaiPrototope/Images.xcassets/cloud.imageset/cloud.png -------------------------------------------------------------------------------- /OhaiPrototope/Images.xcassets/cloud.imageset/cloud@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Khan/OhaiPrototope/1112a442c7ff94f2d7d5e8c2bd2e8f28ceaf4600/OhaiPrototope/Images.xcassets/cloud.imageset/cloud@2x.png -------------------------------------------------------------------------------- /OhaiPrototope/Images.xcassets/drop.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "drop.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "drop@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /OhaiPrototope/Images.xcassets/drop.imageset/drop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Khan/OhaiPrototope/1112a442c7ff94f2d7d5e8c2bd2e8f28ceaf4600/OhaiPrototope/Images.xcassets/drop.imageset/drop.png -------------------------------------------------------------------------------- /OhaiPrototope/Images.xcassets/drop.imageset/drop@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Khan/OhaiPrototope/1112a442c7ff94f2d7d5e8c2bd2e8f28ceaf4600/OhaiPrototope/Images.xcassets/drop.imageset/drop@2x.png -------------------------------------------------------------------------------- /OhaiPrototope/Images.xcassets/sparkles.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "sparkles.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "sparkles@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /OhaiPrototope/Images.xcassets/sparkles.imageset/sparkles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Khan/OhaiPrototope/1112a442c7ff94f2d7d5e8c2bd2e8f28ceaf4600/OhaiPrototope/Images.xcassets/sparkles.imageset/sparkles.png -------------------------------------------------------------------------------- /OhaiPrototope/Images.xcassets/sparkles.imageset/sparkles@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Khan/OhaiPrototope/1112a442c7ff94f2d7d5e8c2bd2e8f28ceaf4600/OhaiPrototope/Images.xcassets/sparkles.imageset/sparkles@2x.png -------------------------------------------------------------------------------- /OhaiPrototope/Images.xcassets/star.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "star.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OhaiPrototope/Images.xcassets/star.imageset/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Khan/OhaiPrototope/1112a442c7ff94f2d7d5e8c2bd2e8f28ceaf4600/OhaiPrototope/Images.xcassets/star.imageset/star.png -------------------------------------------------------------------------------- /OhaiPrototope/Images.xcassets/unicorn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "unicorn-1.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OhaiPrototope/Images.xcassets/unicorn.imageset/unicorn-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Khan/OhaiPrototope/1112a442c7ff94f2d7d5e8c2bd2e8f28ceaf4600/OhaiPrototope/Images.xcassets/unicorn.imageset/unicorn-1.png -------------------------------------------------------------------------------- /OhaiPrototope/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | org.khanacademy.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations~ipad 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationPortraitUpsideDown 35 | UIInterfaceOrientationLandscapeLeft 36 | UIInterfaceOrientationLandscapeRight 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /OhaiPrototope/LayoutScene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayoutScene.swift 3 | // OhaiPrototope 4 | // 5 | // Created by Jason Brennan on 2015-02-13. 6 | // Copyright (c) 2015 Prototope Research Facility. All rights reserved. 7 | // 8 | 9 | import Prototope 10 | 11 | class LayoutScene { 12 | 13 | 14 | init() { 15 | Layer.root.backgroundColor = Color(hex: 0xFFF5D9) 16 | 17 | var layers = [Layer]() 18 | 19 | var lastLayer: Layer? = nil 20 | 21 | for index in 0..<5 { 22 | let layer = Layer(parent: Layer.root, name: "layer", viewClass: nil) 23 | 24 | layer.size = Size(width: 100, height: 100) 25 | 26 | if let previousLayer = lastLayer { 27 | layer.moveBelowSiblingLayer(previousLayer, margin: 20) 28 | } else { 29 | layer.originY = 40 30 | } 31 | layer.moveToHorizontalCenterOfParentLayer() 32 | 33 | layer.backgroundColor = Color(hex: 0x70B21A) 34 | layer.cornerRadius = 5 35 | layers.append(layer) 36 | lastLayer = layer 37 | } 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /OhaiPrototope/RotationGesturePlaypen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RotationGesture.swift 3 | // OhaiPrototope 4 | // 5 | // Created by Saniul Ahmed on 27/01/2015. 6 | // Copyright (c) 2015 Marcos Ojeda. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | import Prototope 12 | 13 | // Draws a large white rounded-rectangle that can be rotated with a two-finger gesture. 14 | // The position of the rectangle is fixed and unaffected by the position of touches 15 | // participating in the rotation gesture. After the gesture ends, the rectangle's 16 | // rotation resets using a dynamic animator with the gesture's terminating velocity. 17 | class RotationGesturePlaypen { 18 | 19 | var needyLayer: Layer! 20 | 21 | init(){ 22 | Layer.root.backgroundColor = Color(hex: 0x535F71) 23 | makeNeedyLayer() 24 | 25 | var initialRotation: Double = 0 26 | needyLayer.gestures.append(RotationGesture( handler: { phase, sequence in 27 | 28 | let rotation = sequence.currentSample.rotationRadians 29 | let velocity = sequence.currentSample.velocityRadians 30 | 31 | if phase == .Began { 32 | initialRotation = self.needyLayer.rotationRadians 33 | } 34 | 35 | self.needyLayer.rotationRadians = initialRotation + rotation 36 | 37 | if phase == .Ended { 38 | initialRotation = 0 39 | self.needyLayer.animators.rotationRadians.target = 0 40 | self.needyLayer.animators.rotationRadians.springBounciness = 10 41 | self.needyLayer.animators.rotationRadians.velocity = velocity 42 | } 43 | 44 | })) 45 | } 46 | 47 | func gimmeSquare(x:Int = 384) -> Layer! { 48 | // // return a rounded white square at some x value (defaults to 384) 49 | let tempLayer = Layer(parent: Layer.root) 50 | // tunable(100, name: "layer width") { width in tempLayer.width = width } 51 | tempLayer.width = 200 52 | tempLayer.height = 200 53 | tempLayer.backgroundColor = Color(white: 1, alpha: 1) 54 | tempLayer.cornerRadius = 5 55 | tempLayer.x = Double(x) 56 | tempLayer.y = 512 57 | 58 | return tempLayer 59 | } 60 | 61 | func makeNeedyLayer(){ 62 | needyLayer = gimmeSquare() 63 | } 64 | 65 | } 66 | 67 | -------------------------------------------------------------------------------- /OhaiPrototope/SceneIndex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneIndex.swift 3 | // OhaiPrototope 4 | // 5 | // Created by Andy Matuschak on 2/6/15. 6 | // Copyright (c) 2015 Marcos Ojeda. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Scene { 12 | let name: String 13 | let constructor: () -> AnyObject // what *are* types, anyway? 14 | 15 | static let sceneIndex: [Scene] = [ 16 | Scene(name: "Touch animators", constructor: { TouchAnimators() }), 17 | Scene(name: "Shadow light source", constructor: { ShadowLightsource() }), 18 | Scene(name: "Touch events", constructor: { TouchEvents() }), 19 | Scene(name: "Touch unicorns", constructor: { TouchUnicorns() }), 20 | Scene(name: "Rotation gesture playpen", constructor: { RotationGesturePlaypen() }), 21 | Scene(name: "Throw cats", constructor: { ThrowCats() }), 22 | Scene(name: "Particles", constructor: { TouchParticles() }), 23 | Scene(name: "Video", constructor: { VideoScene() }), 24 | Scene(name: "Force pull", constructor: { ForcePull() }), 25 | Scene(name: "Scrolly", constructor: { ScrollScene() }), 26 | Scene(name: "Layout", constructor: { LayoutScene() }), 27 | Scene(name: "Speechy", constructor: { SpeechyScene() }), 28 | Scene(name: "Tree Maker", constructor: { TreeMaker() }), 29 | Scene(name: "Shapes", constructor: { ShapeScene() }) 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /OhaiPrototope/SceneListingViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneListingViewController.swift 3 | // OhaiPrototope 4 | // 5 | // Created by Andy Matuschak on 2/6/15. 6 | // Copyright (c) 2015 Marcos Ojeda. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SceneListingViewController: UITableViewController { 12 | private let dataSource: SceneListingDataSource 13 | private var sceneActivationHandler: Scene -> () 14 | 15 | init(scenes: [Scene], sceneActivationHandler: Scene -> ()) { 16 | dataSource = SceneListingDataSource(scenes: scenes) 17 | self.sceneActivationHandler = sceneActivationHandler 18 | super.init(nibName: nil, bundle: nil) 19 | tableView.dataSource = dataSource 20 | tableView.delegate = self 21 | tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Scene") 22 | } 23 | 24 | required init(coder aDecoder: NSCoder) { 25 | fatalError("init(coder:) has intentionally not been implemented") 26 | } 27 | 28 | override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { 29 | sceneActivationHandler(dataSource.scenes[indexPath.row]) 30 | } 31 | } 32 | 33 | private class SceneListingDataSource: NSObject, UITableViewDataSource { 34 | var scenes: [Scene] 35 | init(scenes: [Scene]) { 36 | self.scenes = scenes 37 | super.init() 38 | } 39 | @objc private func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 40 | return scenes.count 41 | } 42 | @objc private func numberOfSectionsInTableView(tableView: UITableView) -> Int { 43 | return 1 44 | } 45 | @objc private func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 46 | let cell = tableView.dequeueReusableCellWithIdentifier("Scene", forIndexPath: indexPath) 47 | cell.textLabel!.text = scenes[indexPath.row].name 48 | return cell 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /OhaiPrototope/SceneViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneViewController.swift 3 | // OhaiPrototope 4 | // 5 | // Created by Marcos Ojeda on 1/10/15. 6 | // Copyright (c) 2015 Marcos Ojeda. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Prototope 11 | 12 | class SceneViewController: UIViewController { 13 | var backActionHandler: () -> Void = {} 14 | 15 | init(scene: Scene) { 16 | super.init(nibName: nil, bundle: nil) 17 | // set the view controller's view as the Prototope Root View 18 | Environment.currentEnvironment = Environment.defaultEnvironmentWithRootView(self.view) 19 | 20 | mainScene = scene.constructor() 21 | } 22 | 23 | required init?(coder aDecoder: NSCoder) { 24 | fatalError("init(coder:) has intentionally not been implemented") 25 | } 26 | 27 | // likely some form of 'scene' 28 | var mainScene: AnyObject! 29 | 30 | func handleKeyCommand(command: UIKeyCommand!) { 31 | switch command.input { 32 | case UIKeyInputEscape: 33 | backActionHandler() 34 | default: 35 | return 36 | } 37 | } 38 | 39 | // needed to let vc handle keypresses 40 | override func canBecomeFirstResponder() -> Bool { 41 | return true 42 | } 43 | 44 | override var keyCommands: [UIKeyCommand]? { 45 | get { 46 | let escape = UIKeyCommand(input: UIKeyInputEscape, modifierFlags: [], action: "handleKeyCommand:") 47 | return [escape] 48 | } 49 | } 50 | 51 | } 52 | 53 | -------------------------------------------------------------------------------- /OhaiPrototope/ScrollScene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScrollScene.swift 3 | // OhaiPrototope 4 | // 5 | // Created by Jason Brennan on 2015-02-11. 6 | // Copyright (c) 2015 Prototope Research Facility. All rights reserved. 7 | // 8 | 9 | import Prototope 10 | 11 | /** Scrolls! */ 12 | class ScrollScene { 13 | let scrollLayer: ScrollLayer 14 | 15 | let lilLayers: [Layer] 16 | 17 | init() { 18 | Layer.root.backgroundColor = Color(hex: 0xFFF5D9) 19 | 20 | 21 | self.scrollLayer = ScrollLayer(parent: Layer.root, name: "scroller") 22 | 23 | self.scrollLayer.size = Size(width: 200, height: 500) 24 | self.scrollLayer.frame.origin = Point(x: 200, y: 200) 25 | 26 | self.scrollLayer.backgroundColor = Color.white 27 | self.scrollLayer.cornerRadius = 5 28 | 29 | var layers = [Layer]() 30 | for index in 0..<5 { 31 | let layer = Layer(parent: self.scrollLayer, name: "layer", viewClass: nil) 32 | 33 | layer.size = Size(width: self.scrollLayer.width - 20, height: 100) 34 | layer.frame.origin = Point(x: 10.0, y: Double(index) * 140.0) 35 | layer.backgroundColor = Color(hex: 0x70B21A) 36 | layer.cornerRadius = 5 37 | layers.append(layer) 38 | 39 | } 40 | 41 | self.lilLayers = layers 42 | self.scrollLayer.updateScrollableSizeToFitSublayers() 43 | } 44 | } -------------------------------------------------------------------------------- /OhaiPrototope/ShadowLightsource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShadowLightsource.swift 3 | // OhaiPrototope 4 | // 5 | // Created by Marcos Ojeda on 1/23/15. 6 | // Copyright (c) 2015 Marcos Ojeda. All rights reserved. 7 | // 8 | 9 | import Prototope 10 | 11 | class ShadowLightsource { 12 | 13 | var box: Layer! 14 | var boxShadow: Shadow! 15 | 16 | init (){ 17 | Layer.root.backgroundColor = Color(hex: 0x4DD0E1) 18 | box = gimmeSquare() 19 | setupShadowyLayer() 20 | 21 | // as you pan around, use the finger's location to set the position of the light 22 | Layer.root.gestures.append(PanGesture(handler: { phase, centroidSequenc in 23 | var finger: Point = centroidSequenc.currentSample.globalLocation 24 | self.boxShadow.radius = self.radiusForPoints(finger, p2: self.box.position) 25 | self.boxShadow.offset = self.offsetSize(self.box.position, movable: finger) 26 | 27 | self.box.shadow = self.boxShadow 28 | })) 29 | } 30 | 31 | /** 32 | * offsetSize, returns a Size sort of like a mini vector from 33 | * the movable point to the anchor point 34 | */ 35 | func offsetSize(anchor: Point, movable: Point) -> Size { 36 | let delta = anchor - movable 37 | let width = Layer.root.width / 2; 38 | let height = Layer.root.height / 2; 39 | let maxHeight = 15.0 40 | let maxWidth = 12.0 41 | 42 | let offsetWidth: Double = map(delta.x, 43 | fromInterval: (-1 * width, width), 44 | toInterval: (-1.0 * maxWidth, maxWidth)) 45 | let offsetHeight: Double = map(delta.y, 46 | fromInterval:(-1 * height, height), 47 | toInterval:(-1 * maxHeight, maxHeight)) 48 | 49 | return Size(width: offsetWidth, height: offsetHeight) 50 | } 51 | 52 | /** 53 | * radiusForPoints returns the a scaled cartesian distance between two points 54 | * @type {[type]} 55 | */ 56 | func radiusForPoints(p1: Point, p2: Point, max: Double = 25) -> Double { 57 | let radius: Double = p1.distanceToPoint(p2) 58 | let maxRadius = sqrt(pow(Layer.root.bounds.size.height / 2, 2) + 59 | pow(Layer.root.bounds.size.height / 2, 2)); 60 | return map(radius, fromInterval:(0, maxRadius), toInterval:(0, max)) 61 | } 62 | 63 | /** 64 | * gimmeSquare returns a vertically centered rounded white square 65 | * optionally at some x value 66 | */ 67 | func gimmeSquare(x:Int = 324) -> Layer! { 68 | let tempLayer = Layer(parent: Layer.root) 69 | tunable(100, name: "layer width") { width in tempLayer.width = width } 70 | tempLayer.height = 100 71 | tempLayer.backgroundColor = Color(white: 1, alpha: 1) 72 | tempLayer.cornerRadius = 5 73 | tempLayer.x = Double(x) 74 | tempLayer.y = 512 75 | return tempLayer 76 | } 77 | 78 | /** 79 | * sets up an initial shadow on the box in our scene 80 | */ 81 | func setupShadowyLayer() { 82 | let shadowSize: Size = Size(width:0.0, height:-3.0) 83 | boxShadow = Shadow( 84 | color: Color(white:0.2, alpha: 1), 85 | alpha: 0.8, 86 | offset: shadowSize, 87 | radius: 10.0) 88 | 89 | self.box.shadow = boxShadow 90 | } 91 | 92 | 93 | } 94 | -------------------------------------------------------------------------------- /OhaiPrototope/ShapeScene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShapeScene.swift 3 | // OhaiPrototope 4 | // 5 | // Created by Jason Brennan on Mar-27-2015. 6 | // Copyright (c) 2015 Prototope Research Facility. All rights reserved. 7 | // 8 | 9 | import Prototope 10 | 11 | class ShapeScene { 12 | 13 | var touchesToFingerCircles = [UITouchID: ShapeLayer]() 14 | var traceLine: ShapeLayer? = nil 15 | let grey = Color(hex: 0xEAEAEA) 16 | 17 | var line: Line? = nil 18 | var currentSketch: ShapeLayer? = nil 19 | var currentSketchTouchID: UITouchID? = nil 20 | var startPoint: Point? = nil 21 | var sketchedLines = [ShapeLayer]() 22 | 23 | init() { 24 | Layer.root.backgroundColor = Color.white 25 | 26 | 27 | // There's a bug in the touch code for drawing lines. 28 | Layer.root.touchBeganHandler = { touchSequence in 29 | 30 | 31 | if self.currentlyDrawingALine() { 32 | return 33 | } 34 | 35 | 36 | if self.touchesToFingerCircles.count >= 2 { 37 | 38 | // starting to draw a line! 39 | let touchPoint = touchSequence.currentSample.globalLocation 40 | self.startPoint = touchPoint 41 | self.currentSketch = ShapeLayer(lineFromFirstPoint: touchPoint, toSecondPoint: touchPoint) 42 | self.currentSketch?.strokeColor = Color.black 43 | self.currentSketch?.strokeWidth = 5 44 | self.currentSketchTouchID = touchSequence.id 45 | 46 | return 47 | } 48 | 49 | 50 | let fingerCircle = ShapeLayer(circleCenter: touchSequence.currentSample.globalLocation, radius: 30) 51 | fingerCircle.fillColor = Color.white 52 | fingerCircle.strokeColor = self.grey 53 | self.touchesToFingerCircles[touchSequence.id] = fingerCircle 54 | 55 | self.updateLineWithTouchSequence(touchSequence) 56 | } 57 | 58 | Layer.root.touchMovedHandler = { touchSequence in 59 | 60 | let circle = self.touchesToFingerCircles[touchSequence.id] 61 | 62 | 63 | 64 | 65 | if touchSequence.id == self.currentSketchTouchID { 66 | 67 | self.currentSketch?.parent = nil 68 | self.currentSketch = nil 69 | 70 | // This is a stopgap until there's a better way to change the points of a shape and have the frame update 71 | let firstPoint = self.line!.pointBySolvingForYGivenX(self.startPoint!.x) 72 | let touchPoint = touchSequence.currentSample.globalLocation 73 | let secondPoint = self.line!.pointBySolvingForYGivenX(touchPoint.x) 74 | 75 | self.currentSketch = ShapeLayer(lineFromFirstPoint: firstPoint, toSecondPoint: secondPoint) 76 | self.currentSketch?.strokeColor = Color(hex: 0xF84944) 77 | self.currentSketch?.strokeWidth = 5 78 | self.currentSketch?.lineCapStyle = .Round 79 | 80 | } else if !self.currentlyDrawingALine() { 81 | // Update the touch circles and line iff we're not sketching 82 | circle?.position = touchSequence.currentSample.globalLocation 83 | 84 | self.updateLineWithTouchSequence(touchSequence) 85 | } 86 | } 87 | 88 | 89 | Layer.root.touchEndedHandler = { touchSequence in 90 | 91 | let circle = self.touchesToFingerCircles[touchSequence.id] 92 | 93 | // if we can't find the circle, and we're drawing a line, then this touch belongs to the sketch touch 94 | if touchSequence.id == self.currentSketchTouchID { 95 | if let sketch = self.currentSketch { 96 | self.sketchedLines.append(sketch) 97 | self.currentSketch = nil 98 | self.currentSketchTouchID = nil 99 | } 100 | return 101 | } 102 | 103 | circle?.parent = nil 104 | self.touchesToFingerCircles.removeValueForKey(touchSequence.id) 105 | 106 | if self.touchesToFingerCircles.count < 2 { 107 | self.traceLine?.parent = nil 108 | self.traceLine = nil 109 | } 110 | } 111 | 112 | } 113 | 114 | 115 | func updateLineWithTouchSequence(touchSequence: TouchSequence) { 116 | if self.touchesToFingerCircles.count == 2 { 117 | 118 | let p1 = self.leftmostCircle()!.position 119 | let p2 = self.rightmostCircle()!.position 120 | 121 | self.line = Line(p1: p1, p2: p2) 122 | 123 | 124 | // remove the line first...don't have a good method of changing line points right now? 125 | self.traceLine?.parent = nil 126 | self.traceLine = nil 127 | 128 | 129 | let firstPoint = self.line!.pointBySolvingForYGivenX(0) 130 | let secondPoint = self.line!.pointBySolvingForYGivenX(Layer.root.width) 131 | 132 | self.traceLine = ShapeLayer(lineFromFirstPoint: firstPoint, toSecondPoint: secondPoint) 133 | self.traceLine?.strokeColor = self.grey 134 | } 135 | } 136 | 137 | 138 | func currentlyDrawingALine() -> Bool { 139 | return self.currentSketch != nil 140 | } 141 | 142 | func leftmostCircle() -> ShapeLayer? { 143 | var leftmostCircle: ShapeLayer? = nil 144 | 145 | for key in self.touchesToFingerCircles { 146 | let circle = self.touchesToFingerCircles[key.0] 147 | if leftmostCircle == nil || circle!.position.x < leftmostCircle?.position.x { 148 | leftmostCircle = circle 149 | } 150 | } 151 | 152 | return leftmostCircle 153 | } 154 | 155 | 156 | func rightmostCircle() -> ShapeLayer? { 157 | var rightmostCircle: ShapeLayer? = nil 158 | 159 | for key in self.touchesToFingerCircles { 160 | let circle = self.touchesToFingerCircles[key.0] 161 | if rightmostCircle == nil || circle!.position.x > rightmostCircle?.position.x { 162 | rightmostCircle = circle 163 | } 164 | } 165 | 166 | return rightmostCircle 167 | } 168 | 169 | } 170 | 171 | 172 | /** Represents a line running through two points in 2D space. */ 173 | struct Line { 174 | 175 | /** The first point. */ 176 | let p1: Point 177 | 178 | /** The second point. */ 179 | let p2: Point 180 | 181 | 182 | /** The slope between the two points. This is known as the 'm' in 'y = mx + b'. */ 183 | var slope: Double { 184 | return p1.slopeToPoint(p2) ?? 0.0 185 | } 186 | 187 | 188 | /** The y-intercept of the line. This is known as the 'b' in 'y = mx + b'. */ 189 | var yIntercept: Double { 190 | return p1.y - (slope * p1.x) 191 | } 192 | 193 | 194 | /** Solves for Y along the line given an x value, and returns a point of the coordinates. */ 195 | func pointBySolvingForYGivenX(x: Double) -> Point { 196 | return Point(x: x, y: slope * x + yIntercept) 197 | } 198 | } -------------------------------------------------------------------------------- /OhaiPrototope/SpeechyScene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SpeechyScene.swift 3 | // OhaiPrototope 4 | // 5 | // Created by Jason Brennan on 2015-02-17. 6 | // Copyright (c) 2015 Prototope Research Facility. All rights reserved. 7 | // 8 | 9 | import Prototope 10 | 11 | class SpeechyScene { 12 | 13 | 14 | init() { 15 | Layer.root.backgroundColor = Color(hex: 0xFFF5D9) 16 | 17 | var lastY = 40.0 18 | 19 | for index in 1..<5 { 20 | 21 | let textLayer = TextLayer(parent: Layer.root, name: "\(index)") 22 | textLayer.text = "\(index)" 23 | textLayer.fontSize = 40 24 | textLayer.originX = 30 25 | textLayer.originY = lastY 26 | lastY = textLayer.frameMaxY + 40 27 | 28 | textLayer.gestures.append(TapGesture (numberOfTouchesRequired: 1, numberOfTapsRequired: 1) { _ in 29 | Speech.say(textLayer.text!) 30 | }) 31 | 32 | } 33 | 34 | 35 | let textLayer = TextLayer(parent: Layer.root, name: nil) 36 | textLayer.text = "Tap a number to have it read out loud" 37 | textLayer.fontSize = 30 38 | textLayer.moveToCenterOfParentLayer() 39 | textLayer.gestures.append(TapGesture (numberOfTouchesRequired: 1, numberOfTapsRequired: 1) { _ in 40 | Speech.say("No silly, tap a number.") 41 | }) 42 | 43 | } 44 | } -------------------------------------------------------------------------------- /OhaiPrototope/ThrowCats.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ThrowUnicorns.swift 3 | // OhaiPrototope 4 | // 5 | // Because who doesn't want to throw Cats and also velocity 6 | // Copyright (c) 2015 May-Li Khoe. All rights reserved. 7 | // 8 | 9 | import Prototope 10 | 11 | class ThrowCats { 12 | 13 | 14 | var catLayer: Layer! 15 | 16 | 17 | init(){ 18 | Layer.root.backgroundColor = Color(hex: 0x32FFFB) 19 | makeCatLayer() 20 | 21 | 22 | } 23 | 24 | func makeCatLayer() { 25 | 26 | catLayer = Layer(parent: Layer.root, imageName: "cat") 27 | catLayer.x = 400 28 | catLayer.y = 512 29 | 30 | //insert code to bring cat back here. 31 | 32 | catLayer.gestures.append(PanGesture(cancelsTouchesInLayer: false) { phase, sequence in 33 | switch phase { 34 | case .Began: break // Do nothing 35 | case .Changed: 36 | self.catLayer.position += sequence.currentSample.locationInLayer(self.catLayer.parent!) - sequence.previousSample!.locationInLayer(self.catLayer.parent!) 37 | case .Ended, .Cancelled: 38 | var catVelocity = sequence.currentVelocityInLayer(self.catLayer.parent!) 39 | var catGoal = Point(x:(self.catLayer.position.x + catVelocity.x), y: (self.catLayer.position.y + catVelocity.y)) 40 | self.catLayer.animators.position.target = catGoal 41 | self.catLayer.animators.position.velocity = sequence.currentVelocityInLayer(self.catLayer.parent!) 42 | self.catLayer.animators.position.springBounciness = 0 //no springiness for this cat 43 | 44 | } 45 | }) 46 | 47 | 48 | 49 | 50 | 51 | 52 | } 53 | 54 | 55 | 56 | 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /OhaiPrototope/TouchAnimators.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainScene.swift 3 | // OhaiPrototope 4 | // 5 | // Modeled after http://framerjs.com/examples/preview/#click-events.framer#code 6 | // Copyright (c) 2015 Marcos Ojeda. All rights reserved. 7 | // 8 | 9 | import Prototope 10 | 11 | class TouchAnimators { 12 | 13 | var spinnyLayer: Layer! 14 | var needyLayer: Layer! 15 | 16 | init(){ 17 | Layer.root.backgroundColor = Color(hex: 0x535F55) 18 | makeSpinnyLayer() 19 | makeNeedyLayer() 20 | } 21 | 22 | func gimmeSquare(x:Int = 324) -> Layer! { 23 | // return a rounded white square at some x value (defaults to 324) 24 | 25 | let tempLayer = Layer(parent: Layer.root) 26 | // tunable(100, name: "layer width") { width in tempLayer.width = width } 27 | tempLayer.width = 100 28 | tempLayer.height = 100 29 | tempLayer.backgroundColor = Color(white: 1, alpha: 1) 30 | tempLayer.cornerRadius = 5 31 | tempLayer.x = Double(x) 32 | tempLayer.y = 512 33 | 34 | return tempLayer 35 | } 36 | 37 | func makeSpinnyLayer(){ 38 | // touching this layer will rotate it 90 degrees to the right 39 | spinnyLayer = gimmeSquare() 40 | 41 | spinnyLayer.touchBeganHandler = { _ in 42 | Layer.animateWithDuration(0.35, curve: .EaseInOut, animations: { 43 | self.spinnyLayer.rotationDegrees = 90 44 | }) 45 | } 46 | 47 | spinnyLayer.touchEndedHandler = { _ in 48 | Layer.animateWithDuration(0.35, curve: .EaseInOut, animations: { 49 | self.spinnyLayer.rotationDegrees = 0 50 | }) 51 | } 52 | } 53 | 54 | func makeNeedyLayer(){ 55 | // this layer will also rotate 90 degrees to the right and recoil 56 | // from your touch for as long as you press down on it. Letting go 57 | // will return it to its original state 58 | needyLayer = gimmeSquare(444) 59 | 60 | // starting to touch it will rotate and scale it 61 | needyLayer.touchBeganHandler = { _ in 62 | self.needyLayer.animators.rotationRadians.target = 1.57 63 | self.needyLayer.animators.rotationRadians.springBounciness = 6.0 64 | } 65 | 66 | // letting go restores the values 67 | needyLayer.touchEndedHandler = { _ in 68 | self.needyLayer.animators.rotationRadians.target = 0 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /OhaiPrototope/TouchEvents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainScene.swift 3 | // OhaiPrototope 4 | // 5 | // Modeled after http://framerjs.com/examples/preview/#click-events.framer#code 6 | // Copyright (c) 2015 Marcos Ojeda. All rights reserved. 7 | // 8 | 9 | import Prototope 10 | 11 | class TouchEvents { 12 | 13 | var spinnyLayer: Layer! 14 | var needyLayer: Layer! 15 | 16 | init(){ 17 | Layer.root.backgroundColor = Color(hex: 0x535F71) 18 | makeSpinnyLayer() 19 | makeNeedyLayer() 20 | } 21 | 22 | func gimmeSquare(x:Int = 324) -> Layer! { 23 | // return a rounded white square at some x value (defaults to 324) 24 | 25 | let tempLayer = Layer(parent: Layer.root) 26 | tunable(100, name: "layer width") { width in tempLayer.width = width } 27 | tempLayer.height = 100 28 | tempLayer.backgroundColor = Color(white: 1, alpha: 1) 29 | tempLayer.cornerRadius = 5 30 | tempLayer.x = Double(x) 31 | tempLayer.y = 512 32 | 33 | return tempLayer 34 | } 35 | 36 | func makeSpinnyLayer(){ 37 | // touching this layer will rotate it 90 degrees to the right 38 | spinnyLayer = gimmeSquare() 39 | 40 | spinnyLayer.touchBeganHandler = { _ in 41 | Layer.animateWithDuration(0.3, curve: .EaseInOut, animations: { 42 | self.spinnyLayer.rotationDegrees += 90 43 | }) 44 | } 45 | } 46 | 47 | func makeNeedyLayer(){ 48 | // this layer will also rotate 90 degrees to the right and recoil 49 | // from your touch for as long as you press down on it. Letting go 50 | // will return it to its original state 51 | needyLayer = gimmeSquare(444) 52 | 53 | // starting to touch it will rotate and scale it 54 | needyLayer.touchBeganHandler = { _ in 55 | Layer.animateWithDuration(0.7, curve: .EaseInOut, animations: { 56 | self.needyLayer.rotationDegrees = 90 57 | self.needyLayer.scale = 0.8 58 | }) 59 | } 60 | 61 | // letting go restores the values 62 | needyLayer.touchEndedHandler = { _ in 63 | Layer.animateWithDuration(0.5, curve: .EaseInOut, animations: { 64 | self.needyLayer.rotationDegrees = 0 65 | self.needyLayer.scale = 1 66 | }) 67 | } 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /OhaiPrototope/TouchParticles.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TouchParticles.swift 3 | // OhaiPrototope 4 | // 5 | // Gives ya particle fever. 6 | // Copyright (c) 2015 Jason Brennan. All rights reserved. 7 | // 8 | 9 | import Prototope 10 | 11 | /** Touch the cloud, make it rain; touch the layer, make it sparkle. */ 12 | class TouchParticles { 13 | 14 | var cloudLayer: Layer! 15 | var sparkleLayer: Layer! 16 | 17 | var rainEmitter: ParticleEmitter? 18 | var sparkleEmitter: ParticleEmitter! 19 | 20 | 21 | init() { 22 | Layer.root.backgroundColor = Color(hex: 0xFFF5D9) 23 | makeCloudLayer() 24 | makeSparkleLayer() 25 | 26 | 27 | } 28 | 29 | func makeCloudLayer() { 30 | self.cloudLayer = Layer(parent: Layer.root, imageName: "cloud") 31 | self.cloudLayer.x = 500 32 | self.cloudLayer.y = 100 33 | 34 | self.cloudLayer.gestures.append(TapGesture (numberOfTouchesRequired: 1, numberOfTapsRequired: 1) { _ in 35 | self.makeItRain() 36 | }) 37 | 38 | self.cloudLayer.gestures.append(PanGesture( handler: { _, centroidSequenc in 39 | var finger: Point = centroidSequenc.currentSample.globalLocation 40 | self.cloudLayer.x = finger.x 41 | self.rainEmitter?.x = finger.x 42 | })) 43 | } 44 | 45 | 46 | func makeSparkleLayer() { 47 | self.sparkleLayer = Layer(parent: Layer.root) 48 | self.sparkleLayer.x = 324 49 | self.sparkleLayer.y = 512 50 | self.sparkleLayer.width = 100 51 | self.sparkleLayer.height = 100 52 | self.sparkleLayer.cornerRadius = 5 53 | self.sparkleLayer.backgroundColor = Color(hex: 0x4A4A4A) 54 | 55 | self.sparkleLayer.gestures.append(TapGesture (numberOfTouchesRequired: 1, numberOfTapsRequired: 1) { _ in 56 | self.makeItSparkle() 57 | }) 58 | } 59 | 60 | func makeItRain() { 61 | let raindrop = Particle(imageName: "drop", preset: .Rain) 62 | let rainEmitter = ParticleEmitter(particle: raindrop) 63 | self.rainEmitter = rainEmitter 64 | Layer.root.addParticleEmitter(rainEmitter) 65 | 66 | // Layer sets these properties automatically, but I want to reset them manually. 67 | rainEmitter.size = self.cloudLayer.size 68 | rainEmitter.position = self.cloudLayer.position 69 | } 70 | 71 | 72 | func makeItSparkle() { 73 | let sparkle = Particle(imageName: "sparkles", preset: .Sparkle) 74 | self.sparkleEmitter = ParticleEmitter(particle: sparkle) 75 | self.sparkleEmitter.shape = kCAEmitterLayerRectangle 76 | self.sparkleLayer.addParticleEmitter(self.sparkleEmitter, forDuration: 3) 77 | afterDuration(1) { 78 | self.sparkleEmitter.birthRate = 0 79 | } 80 | 81 | } 82 | } -------------------------------------------------------------------------------- /OhaiPrototope/TouchUnicorns.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TouchUnicorns.swift 3 | // OhaiPrototope 4 | // 5 | // Because Unicorns love to be touched 6 | // Copyright (c) 2015 May-Li Khoe. All rights reserved. 7 | // 8 | 9 | import Prototope 10 | 11 | class TouchUnicorns { 12 | 13 | var touchMeLayer: Layer! 14 | var unicornLayer: Layer! 15 | 16 | 17 | init(){ 18 | Layer.root.backgroundColor = Color(hex: 0xFF31A0) 19 | //makeTouchMeLayer() 20 | makeUnicornLayer() 21 | 22 | Layer.root.gestures.append(PanGesture( handler: { phase, centroidSequenc in 23 | var finger: Point = centroidSequenc.currentSample.globalLocation 24 | 25 | for touch in centroidSequenc.samples { 26 | var touchLoc: Point = touch.globalLocation 27 | self.gimmeSparkle(touchLoc.x, y:touchLoc.y) 28 | } 29 | 30 | self.unicornLayer.x = finger.x 31 | self.unicornLayer.y = finger.y 32 | self.unicornLayer.zPosition = 1.0 33 | 34 | })) 35 | 36 | } 37 | 38 | func gimmeSparkle(x:Double, y:Double) -> Layer { 39 | //return a sparkle at the x and y value provided 40 | 41 | let sparkleLayer = Layer(parent: Layer.root, imageName: "star") 42 | sparkleLayer.x = Double(x) 43 | sparkleLayer.y = Double(y) 44 | 45 | return sparkleLayer 46 | 47 | } 48 | 49 | func gimmeSquare(x:Int = 324) -> Layer! { 50 | // return a rounded white square at some x value (defaults to 324) 51 | 52 | let tempLayer = Layer(parent: Layer.root) 53 | // tunable(100, name: "layer width") { width in tempLayer.width = width } 54 | tempLayer.width = 100 55 | tempLayer.height = 100 56 | tempLayer.backgroundColor = Color(white: 1, alpha: 1) 57 | tempLayer.cornerRadius = 5 58 | tempLayer.x = Double(x) 59 | tempLayer.y = 512 60 | 61 | return tempLayer 62 | } 63 | 64 | func makeTouchMeLayer(){ 65 | // touching this layer will rotate it 90 degrees to the right 66 | touchMeLayer = gimmeSquare() 67 | 68 | touchMeLayer.touchBeganHandler = { _ in 69 | Layer.animateWithDuration(0.35, curve: .EaseInOut, animations: { 70 | self.touchMeLayer.rotationDegrees = 90 71 | }) 72 | } 73 | 74 | touchMeLayer.touchEndedHandler = { _ in 75 | Layer.animateWithDuration(0.35, curve: .EaseInOut, animations: { 76 | self.touchMeLayer.rotationDegrees = 0 77 | }) 78 | } 79 | } 80 | 81 | func makeUnicornLayer() { 82 | 83 | unicornLayer = Layer(parent: Layer.root, imageName: "unicorn") 84 | unicornLayer.x = 400 85 | unicornLayer.y = 512 86 | 87 | } 88 | 89 | 90 | 91 | 92 | 93 | 94 | } 95 | -------------------------------------------------------------------------------- /OhaiPrototope/TreeMaker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TreeMaker.swift 3 | // OhaiPrototope 4 | // 5 | // Adapted from Bret Victor's tree in Inventing on Principle. 6 | // 7 | 8 | import Prototope 9 | 10 | class TreeMaker { 11 | 12 | 13 | let trunkColor = Color.brown 14 | 15 | var treeContainer: Layer! 16 | var leafContainer: Layer! 17 | var lastAvailableLeafInCache = -1 18 | 19 | init(){ 20 | leafContainer = Layer() 21 | leafContainer.zPosition = 100 22 | 23 | makeTree() 24 | Layer.root.backgroundColor = Color.white 25 | Layer.root.gestures = [ 26 | TapGesture(handler: { _ in 27 | self.makeTree() 28 | }) 29 | ] 30 | } 31 | 32 | 33 | func makeTree() { 34 | if (treeContainer != nil) { 35 | if lastAvailableLeafInCache >= 0 { 36 | leafContainer.removeAllSublayers() 37 | } 38 | lastAvailableLeafInCache = leafContainer.sublayers.count - 1 39 | treeContainer.parent = nil 40 | } 41 | 42 | treeContainer = Layer() 43 | treeContainer.frame = Layer.root.bounds 44 | leafContainer.frame = treeContainer.frame 45 | drawBranches(parent: treeContainer, level: 0, radians: -M_PI / 2, position: Point(x: Layer.root.x, y: Layer.root.height), width: 30) 46 | } 47 | 48 | 49 | func drawBranches(parent parent: Layer, level: Int, radians: Double, position: Point, width: Double) { 50 | let lowerBranchLength = tunable(62, name: "Branch length - lower", min: 1, max: 100) 51 | let upperBranchLength = tunable(3, name: "Branch length - upper", min: 1, max: 100) 52 | let branchLengthRangeFactor = tunable(0.3, name: "Branch length - range factor", min: 0, max: 0.9) 53 | let maxLevel = 12 54 | 55 | var length = map(Double(level), fromInterval: (1, 12), toInterval: (lowerBranchLength, upperBranchLength)) * randomInterval(1 - branchLengthRangeFactor, 1 + branchLengthRangeFactor) 56 | if (level == 0) { 57 | length = tunable(110, name: "trunk length", min: 20, max: 300) 58 | } 59 | 60 | let branchSegment = Layer(parent: parent) 61 | branchSegment.anchorPoint = Point(x: 0, y: 0.5) 62 | branchSegment.backgroundColor = trunkColor 63 | branchSegment.position = position 64 | branchSegment.rotationRadians = radians 65 | branchSegment.width = length 66 | branchSegment.height = width 67 | 68 | let widthFactor = tunable(0.7, name: "Branch width factor", min: 0.1, max: 0.9) 69 | let branchJoinLength = length - width 70 | let branchTipPosition = Point(x: position.x + cos(radians) * branchJoinLength, y: position.y + sin(radians) * branchJoinLength) 71 | 72 | let angleRange = tunable(0.10, name: "Branching angle range", min: 0, max: 0.5) 73 | 74 | if (level < 6) { 75 | drawBranches(parent: parent, level: level + 1, radians: radians + randomInterval(-0.05 - angleRange, -0.05) * M_PI, position: branchTipPosition, width: width * widthFactor) 76 | drawBranches(parent: parent, level: level + 1, radians: radians + randomInterval(0.05 + angleRange, 0.05) * M_PI, position: branchTipPosition, width: width * widthFactor) 77 | } else if (level < 12) { 78 | drawBranches(parent: parent, level: level + 1, radians: radians + randomInterval(-angleRange, angleRange) * M_PI, position: branchTipPosition, width: width * widthFactor) 79 | } 80 | 81 | let leafIntroductionHeight = Double(maxLevel) * tunable(0.3, name: "Leaf introduction height", min: 0, max: 1) 82 | if (level > Int(leafIntroductionHeight)) { 83 | drawLeaves(parent: leafContainer, from: branchSegment.position, to: branchTipPosition) 84 | } 85 | } 86 | 87 | func drawLeaves(parent parent: Layer, from: Point, to: Point) { 88 | let leavesPerBranchSegment = Int(tunable(10, name: "leaves per segment", min: 0, max: 25)) 89 | 90 | for leafIndex in 0.. 0 { 96 | leaf = leafContainer.sublayers[lastAvailableLeafInCache] as! ShapeLayer 97 | leaf.x = x 98 | leaf.y = y 99 | lastAvailableLeafInCache-- 100 | } else { 101 | leaf = makeLeaf(parent: parent, point: Point(x: x, y: y)) 102 | } 103 | 104 | let flowerHue = tunable(0.32, name: "leaf hue", min: 0, max: 1) 105 | let hueRange = 0.05 106 | let flowerSaturation = tunable(0.46, name: "leaf saturation", min: 0, max: 1) 107 | let saturationRange = 0.05 108 | let flowerBrightness = tunable(0.8, name: "leaf brightness", min: 0, max: 1) 109 | let brightnessRange = 0.05 110 | let flowerAlpha = tunable(0.67, name: "leaf alpha", min: 0, max: 1) 111 | leaf.fillColor = Color( 112 | hue: flowerHue + randomInterval(-hueRange, hueRange), 113 | saturation: flowerSaturation + randomInterval(-saturationRange, saturationRange), 114 | brightness: flowerBrightness + randomInterval(-brightnessRange, brightnessRange) 115 | ) 116 | } 117 | } 118 | 119 | func makeLeaf(parent parent: Layer, point: Point) -> ShapeLayer { 120 | let flowerHue = tunable(0.32, name: "leaf hue", min: 0, max: 1) 121 | let hueRange = 0.05 122 | let flowerSaturation = tunable(0.46, name: "leaf saturation", min: 0, max: 1) 123 | let saturationRange = 0.05 124 | let flowerBrightness = tunable(0.8, name: "leaf brightness", min: 0, max: 1) 125 | let brightnessRange = 0.05 126 | let flowerAlpha = tunable(0.67, name: "leaf alpha", min: 0, max: 1) 127 | 128 | 129 | var leaf = ShapeLayer(circleCenter: point, radius: randomInterval(2, 5), parent: parent) 130 | leaf.alpha = flowerAlpha 131 | leaf.strokeColor = nil 132 | leaf.zPosition = 100 133 | 134 | return leaf 135 | } 136 | 137 | func randomInterval(min: Double, _ max: Double) -> Double { 138 | return drand48() * (max - min) + min 139 | } 140 | 141 | var catLayer: Layer! 142 | 143 | 144 | 145 | } -------------------------------------------------------------------------------- /OhaiPrototope/TunableValues.json: -------------------------------------------------------------------------------- 1 | [ 2 | ] -------------------------------------------------------------------------------- /OhaiPrototope/VideoScene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VideoScene.swift 3 | // OhaiPrototope 4 | // 5 | // Created by Jason Brennan on 2015-02-09. 6 | // Copyright (c) 2015 Prototope Research Facility. All rights reserved. 7 | // 8 | 9 | import Prototope 10 | 11 | /** Plays a great video. */ 12 | class VideoScene { 13 | let video: Video! 14 | let videoLayer: VideoLayer 15 | 16 | init() { 17 | Layer.root.backgroundColor = Color(hex: 0xFFF5D9) 18 | self.video = Video(name: "jeff.mp4") 19 | 20 | self.videoLayer = VideoLayer(parent: Layer.root, video: self.video) 21 | 22 | self.videoLayer.size = Size(width: 400, height: 300) 23 | self.videoLayer.x = 200 24 | self.videoLayer.y = 200 25 | 26 | self.videoLayer.play() 27 | } 28 | 29 | 30 | deinit { 31 | // Unless you want a Goldblum ghost. 32 | self.videoLayer.pause() 33 | } 34 | } -------------------------------------------------------------------------------- /OhaiPrototope/jeff.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Khan/OhaiPrototope/1112a442c7ff94f2d7d5e8c2bd2e8f28ceaf4600/OhaiPrototope/jeff.mp4 -------------------------------------------------------------------------------- /OhaiPrototopeTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | org.khanacademy.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /OhaiPrototopeTests/OhaiPrototopeTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OhaiPrototopeTests.swift 3 | // OhaiPrototopeTests 4 | // 5 | // Created by Marcos Ojeda on 1/10/15. 6 | // Copyright (c) 2015 Marcos Ojeda. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | class OhaiPrototopeTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | XCTAssert(true, "Pass") 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measureBlock() { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/Khan/OhaiPrototope.svg?branch=master)](https://travis-ci.org/Khan/OhaiPrototope) 2 | 3 | # OHAI Prototope! 4 | 5 | A test project for getting started using [prototope](https://github.com/khan/prototope). 6 | 7 | ## getting started 8 | 9 | **You'll need Xcode 6.3 to run Prototope!** 10 | 11 | Make sure to use the recursive option on clone to auto init all submodules. This 12 | will pull in both prototope and pop (and allow your environment to build). 13 | 14 | $ git clone --recursive https://github.com/khan/ohaiprototope 15 | 16 | If you have already cloned the repository (without the recursive option), run 17 | the following from inside the project directory to do the same. 18 | 19 | $ git submodule update --init --recursive 20 | 21 | 22 | ### license 23 | 24 | this project (but not its submodules) is released under an [MIT License]() (i'm not really sure how that works considering that lion's share of this project is cribbing from default xcode templates, but i mean, that's just where we've arrived now, isn't it). That basically means you should rename it if you release it. I mean, with a name like ohai prototope, you probably want to rename it, even if you just call it something like HowdyPrototaupe. 25 | 26 | > Copyright (c) 2015 khan academy 27 | 28 | > Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 29 | 30 | > The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 31 | 32 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 33 | -------------------------------------------------------------------------------- /script/LICENSE.md: -------------------------------------------------------------------------------- 1 | **Copyright (c) 2013 Justin Spahr-Summers** 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /script/README.md: -------------------------------------------------------------------------------- 1 | # objc-build-scripts 2 | 3 | This project is a collection of scripts created with two goals: 4 | 5 | 1. To standardize how Objective-C projects are bootstrapped after cloning 6 | 1. To easily build Objective-C projects on continuous integration servers 7 | 8 | ## Scripts 9 | 10 | Right now, there are two important scripts: [`bootstrap`](#bootstrap) and 11 | [`cibuild`](#cibuild). Both are Bash scripts, to maximize compatibility and 12 | eliminate pesky system configuration issues (like setting up a working Ruby 13 | environment). 14 | 15 | The structure of the scripts on disk is meant to follow that of a typical Ruby 16 | project: 17 | 18 | ``` 19 | script/ 20 | bootstrap 21 | cibuild 22 | ``` 23 | 24 | ### bootstrap 25 | 26 | This script is responsible for bootstrapping (initializing) your project after 27 | it's been checked out. Here, you should install or clone any dependencies that 28 | are required for a working build and development environment. 29 | 30 | By default, the script will verify that [xctool][] is installed, then initialize 31 | and update submodules recursively. If any submodules contain `script/bootstrap`, 32 | that will be run as well. 33 | 34 | To check that other tools are installed, you can set the `REQUIRED_TOOLS` 35 | environment variable before running `script/bootstrap`, or edit it within the 36 | script directly. Note that no installation is performed automatically, though 37 | this can always be added within your specific project. 38 | 39 | ### cibuild 40 | 41 | This script is responsible for building the project, as you would want it built 42 | for continuous integration. This is preferable to putting the logic on the CI 43 | server itself, since it ensures that any changes are versioned along with the 44 | source. 45 | 46 | By default, the script will run [`bootstrap`](#bootstrap), look for any Xcode 47 | workspace or project in the working directory, then build all targets/schemes 48 | (as found by `xcodebuild -list`) using [xctool][]. 49 | 50 | You can also specify the schemes to build by passing them into the script: 51 | 52 | ```sh 53 | script/cibuild ReactiveCocoa-Mac ReactiveCocoa-iOS 54 | ``` 55 | 56 | As with the `bootstrap` script, there are several environment variables that can 57 | be used to customize behavior. They can be set on the command line before 58 | invoking the script, or the defaults changed within the script directly. 59 | 60 | ## Getting Started 61 | 62 | To add the scripts to your project, read the contents of this repository into 63 | a `script` folder: 64 | 65 | ``` 66 | $ git remote add objc-build-scripts https://github.com/jspahrsummers/objc-build-scripts.git 67 | $ git fetch objc-build-scripts 68 | $ git read-tree --prefix=script/ -u objc-build-scripts/master 69 | ``` 70 | 71 | Then commit the changes, to incorporate the scripts into your own repository's 72 | history. You can also freely tweak the scripts for your specific project's 73 | needs. 74 | 75 | To merge in upstream changes later: 76 | 77 | ``` 78 | $ git fetch -p objc-build-scripts 79 | $ git merge --ff --squash -Xsubtree=script objc-build-scripts/master 80 | ``` 81 | 82 | [xctool]: https://github.com/facebook/xctool 83 | -------------------------------------------------------------------------------- /script/bootstrap: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export SCRIPT_DIR=$(dirname "$0") 4 | 5 | ## 6 | ## Bootstrap Process 7 | ## 8 | 9 | main () 10 | { 11 | local submodules=$(git submodule status) 12 | local result=$? 13 | 14 | if [ "$result" -ne "0" ] 15 | then 16 | exit $result 17 | fi 18 | 19 | if [ -n "$submodules" ] 20 | then 21 | echo "*** Updating submodules..." 22 | update_submodules 23 | fi 24 | } 25 | 26 | bootstrap_submodule () 27 | { 28 | local bootstrap="script/bootstrap" 29 | 30 | if [ -e "$bootstrap" ] 31 | then 32 | echo "*** Bootstrapping $name..." 33 | "$bootstrap" >/dev/null 34 | else 35 | update_submodules 36 | fi 37 | } 38 | 39 | update_submodules () 40 | { 41 | git submodule sync --quiet && git submodule update --init && git submodule foreach --quiet bootstrap_submodule 42 | } 43 | 44 | export -f bootstrap_submodule 45 | export -f update_submodules 46 | 47 | main 48 | -------------------------------------------------------------------------------- /script/cibuild: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export SCRIPT_DIR=$(dirname "$0") 4 | 5 | ## 6 | ## Configuration Variables 7 | ## 8 | 9 | SCHEMES="$@" 10 | 11 | config () 12 | { 13 | # The workspace to build. 14 | # 15 | # If not set and no workspace is found, the -workspace flag will not be passed 16 | # to `xctool`. 17 | # 18 | # Only one of `XCWORKSPACE` and `XCODEPROJ` needs to be set. The former will 19 | # take precedence. 20 | : ${XCWORKSPACE=$(find_pattern "*.xcworkspace")} 21 | 22 | # The project to build. 23 | # 24 | # If not set and no project is found, the -project flag will not be passed 25 | # to `xctool`. 26 | # 27 | # Only one of `XCWORKSPACE` and `XCODEPROJ` needs to be set. The former will 28 | # take precedence. 29 | : ${XCODEPROJ=$(find_pattern "*.xcodeproj")} 30 | 31 | # A bootstrap script to run before building. 32 | # 33 | # If this file does not exist, it is not considered an error. 34 | : ${BOOTSTRAP="$SCRIPT_DIR/bootstrap"} 35 | 36 | # Extra options to pass to xctool. 37 | : ${XCTOOL_OPTIONS="RUN_CLANG_STATIC_ANALYZER=NO"} 38 | 39 | # A whitespace-separated list of default schemes to build. 40 | # 41 | # Individual names can be quoted to avoid word splitting. 42 | : ${SCHEMES:=$(xcodebuild -list -project "$XCODEPROJ" 2>/dev/null | awk -f "$SCRIPT_DIR/schemes.awk")} 43 | 44 | # A whitespace-separated list of executables that must be present and locatable. 45 | : ${REQUIRED_TOOLS="xctool"} 46 | 47 | export XCWORKSPACE 48 | export XCODEPROJ 49 | export BOOTSTRAP 50 | export XCTOOL_OPTIONS 51 | export SCHEMES 52 | export REQUIRED_TOOLS 53 | } 54 | 55 | ## 56 | ## Build Process 57 | ## 58 | 59 | main () 60 | { 61 | config 62 | 63 | if [ -n "$REQUIRED_TOOLS" ] 64 | then 65 | echo "*** Checking dependencies..." 66 | check_deps 67 | fi 68 | 69 | if [ -f "$BOOTSTRAP" ] 70 | then 71 | echo "*** Bootstrapping..." 72 | "$BOOTSTRAP" || exit $? 73 | fi 74 | 75 | echo "*** The following schemes will be built:" 76 | echo "$SCHEMES" | xargs -n 1 echo " " 77 | echo 78 | 79 | echo "$SCHEMES" | xargs -n 1 | ( 80 | local status=0 81 | 82 | while read scheme 83 | do 84 | build_scheme "$scheme" || status=1 85 | done 86 | 87 | exit $status 88 | ) 89 | } 90 | 91 | check_deps () 92 | { 93 | for tool in $REQUIRED_TOOLS 94 | do 95 | which -s "$tool" 96 | if [ "$?" -ne "0" ] 97 | then 98 | echo "*** Error: $tool not found. Please install it and cibuild again." 99 | exit 1 100 | fi 101 | done 102 | } 103 | 104 | find_pattern () 105 | { 106 | ls -d $1 2>/dev/null | head -n 1 107 | } 108 | 109 | run_xctool () 110 | { 111 | if [ -n "$XCWORKSPACE" ] 112 | then 113 | xctool -workspace "$XCWORKSPACE" $XCTOOL_OPTIONS "$@" 2>&1 114 | elif [ -n "$XCODEPROJ" ] 115 | then 116 | xctool -project "$XCODEPROJ" $XCTOOL_OPTIONS "$@" 2>&1 117 | else 118 | echo "*** No workspace or project file found." 119 | exit 1 120 | fi 121 | } 122 | 123 | parse_build () 124 | { 125 | awk -f "$SCRIPT_DIR/xctool.awk" 2>&1 >/dev/null 126 | } 127 | 128 | build_scheme () 129 | { 130 | local scheme=$1 131 | 132 | echo "*** Building and testing $scheme..." 133 | echo 134 | 135 | local sdkflag= 136 | local action=test 137 | 138 | # Determine whether we can run unit tests for this target. 139 | run_xctool -scheme "$scheme" run-tests | parse_build 140 | 141 | local awkstatus=$? 142 | 143 | if [ "$awkstatus" -eq "1" ] 144 | then 145 | # SDK not found, try for iphonesimulator. 146 | sdkflag="-sdk iphonesimulator" 147 | 148 | # Determine whether the unit tests will run with iphonesimulator 149 | run_xctool $sdkflag -scheme "$scheme" run-tests | parse_build 150 | 151 | awkstatus=$? 152 | 153 | if [ "$awkstatus" -ne "0" ] 154 | then 155 | # Unit tests will not run on iphonesimulator. 156 | sdkflag="" 157 | fi 158 | fi 159 | 160 | if [ "$awkstatus" -ne "0" ] 161 | then 162 | # Unit tests aren't supported. 163 | action=build 164 | fi 165 | 166 | run_xctool $sdkflag -scheme "$scheme" $action 167 | } 168 | 169 | export -f build_scheme 170 | export -f run_xctool 171 | export -f parse_build 172 | 173 | main 174 | -------------------------------------------------------------------------------- /script/schemes.awk: -------------------------------------------------------------------------------- 1 | BEGIN { 2 | FS = "\n"; 3 | } 4 | 5 | /Schemes:/ { 6 | while (getline && $0 != "") { 7 | sub(/^ +/, ""); 8 | print "'" $0 "'"; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /script/targets.awk: -------------------------------------------------------------------------------- 1 | BEGIN { 2 | FS = "\n"; 3 | } 4 | 5 | /Targets:/ { 6 | while (getline && $0 != "") { 7 | if ($0 ~ /Tests/) continue; 8 | 9 | sub(/^ +/, ""); 10 | print "'" $0 "'"; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /script/xcodebuild.awk: -------------------------------------------------------------------------------- 1 | # Exit statuses: 2 | # 3 | # 0 - No errors found. 4 | # 1 - Build or test failure. Errors will be logged automatically. 5 | # 2 - Untestable target. Retry with the "build" action. 6 | 7 | BEGIN { 8 | status = 0; 9 | } 10 | 11 | { 12 | print; 13 | fflush(stdout); 14 | } 15 | 16 | /is not valid for Testing/ { 17 | exit 2; 18 | } 19 | 20 | /[0-9]+: (error|warning):/ { 21 | errors = errors $0 "\n"; 22 | } 23 | 24 | /(TEST|BUILD) FAILED/ { 25 | status = 1; 26 | } 27 | 28 | END { 29 | if (length(errors) > 0) { 30 | print "\n*** All errors:\n" errors; 31 | } 32 | 33 | fflush(stdout); 34 | exit status; 35 | } 36 | -------------------------------------------------------------------------------- /script/xctool.awk: -------------------------------------------------------------------------------- 1 | # Exit statuses: 2 | # 3 | # 0 - No errors found. 4 | # 1 - Wrong SDK. Retry with SDK `iphonesimulator`. 5 | # 2 - Missing target. 6 | 7 | BEGIN { 8 | status = 0; 9 | } 10 | 11 | { 12 | print; 13 | } 14 | 15 | /Testing with the '(.+)' SDK is not yet supported/ { 16 | status = 1; 17 | } 18 | 19 | /does not contain a target named/ { 20 | status = 2; 21 | } 22 | 23 | END { 24 | exit status; 25 | } 26 | --------------------------------------------------------------------------------