├── .gitignore ├── Images ├── demo.gif └── dependency.png ├── LICENSE ├── Pretty.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── octree.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── Pretty ├── AppDelegate.swift ├── Controller │ └── ViewController.swift ├── Extension │ ├── Array+Extension.swift │ ├── CharacterSet+Extension.swift │ ├── Color+Difference.swift │ ├── NSPoint+Extension.swift │ ├── NSRect+Dependency.swift │ └── PrettyRelation+Extension.swift ├── Info.plist ├── Model │ ├── DependencyNode.swift │ └── PrettyRelation.swift ├── Parser │ ├── Curry.swift │ ├── Parser+Extension.swift │ ├── Parser+Runes.swift │ ├── Parser.swift │ ├── PodLockFileParser.swift │ └── Runes.swift ├── Pretty.entitlements ├── RandomColors │ ├── ColorDefinition.swift │ ├── Hue.swift │ ├── Luminosity.swift │ ├── RandomColor.h │ └── RandomColor.swift ├── Resource │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── icon_128x128.png │ │ │ ├── icon_128x128@2x.png │ │ │ ├── icon_16x16.png │ │ │ ├── icon_16x16@2x.png │ │ │ ├── icon_256x256.png │ │ │ ├── icon_256x256@2x.png │ │ │ ├── icon_32x32.png │ │ │ ├── icon_32x32@2x.png │ │ │ ├── icon_512x512.png │ │ │ └── icon_512x512@2x.png │ │ └── Contents.json │ └── Base.lproj │ │ └── Main.storyboard ├── Utils │ ├── ColorDifference.swift │ ├── ColorSpaceConversion.swift │ ├── DependencySort.swift │ └── HexColors.swift └── View │ ├── RelationItemView.swift │ └── RelationView.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/xcode,macos,swift 3 | 4 | ### macOS ### 5 | *.DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | 9 | # Icon must end with two \r 10 | Icon 11 | 12 | # Thumbnails 13 | ._* 14 | 15 | # Files that might appear in the root of a volume 16 | .DocumentRevisions-V100 17 | .fseventsd 18 | .Spotlight-V100 19 | .TemporaryItems 20 | .Trashes 21 | .VolumeIcon.icns 22 | .com.apple.timemachine.donotpresent 23 | 24 | # Directories potentially created on remote AFP share 25 | .AppleDB 26 | .AppleDesktop 27 | Network Trash Folder 28 | Temporary Items 29 | .apdisk 30 | 31 | ### Swift ### 32 | # Xcode 33 | # 34 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 35 | 36 | ## Build generated 37 | build/ 38 | DerivedData/ 39 | 40 | ## Various settings 41 | *.pbxuser 42 | !default.pbxuser 43 | *.mode1v3 44 | !default.mode1v3 45 | *.mode2v3 46 | !default.mode2v3 47 | *.perspectivev3 48 | !default.perspectivev3 49 | xcuserdata/ 50 | 51 | ## Other 52 | *.moved-aside 53 | *.xccheckout 54 | *.xcscmblueprint 55 | 56 | ## Obj-C/Swift specific 57 | *.hmap 58 | *.ipa 59 | *.dSYM.zip 60 | *.dSYM 61 | 62 | ## Playgrounds 63 | timeline.xctimeline 64 | playground.xcworkspace 65 | 66 | # Swift Package Manager 67 | # 68 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 69 | # Packages/ 70 | # Package.pins 71 | .build/ 72 | 73 | # CocoaPods - Refactored to standalone file 74 | 75 | # Carthage - Refactored to standalone file 76 | 77 | # fastlane 78 | # 79 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 80 | # screenshots whenever they are needed. 81 | # For more information about the recommended setup visit: 82 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 83 | 84 | fastlane/report.xml 85 | fastlane/Preview.html 86 | fastlane/screenshots 87 | fastlane/test_output 88 | 89 | ### Xcode ### 90 | # Xcode 91 | # 92 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 93 | 94 | ## Build generated 95 | 96 | ## Various settings 97 | 98 | ## Other 99 | 100 | ### Xcode Patch ### 101 | *.xcodeproj/* 102 | !*.xcodeproj/project.pbxproj 103 | !*.xcodeproj/xcshareddata/ 104 | !*.xcworkspace/contents.xcworkspacedata 105 | /*.gcno 106 | 107 | 108 | # End of https://www.gitignore.io/api/xcode,macos,swift -------------------------------------------------------------------------------- /Images/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/octree/pretty/c5012b2c990337b2f503b3ee343a507f94e99cd4/Images/demo.gif -------------------------------------------------------------------------------- /Images/dependency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/octree/pretty/c5012b2c990337b2f503b3ee343a507f94e99cd4/Images/dependency.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Pretty 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /Pretty.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C239289320791B980071E305 /* ColorDifference.swift in Sources */ = {isa = PBXBuildFile; fileRef = C239289220791B980071E305 /* ColorDifference.swift */; }; 11 | C239289520791D410071E305 /* Color+Difference.swift in Sources */ = {isa = PBXBuildFile; fileRef = C239289420791D410071E305 /* Color+Difference.swift */; }; 12 | C239289720791F4C0071E305 /* ColorSpaceConversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C239289620791F4C0071E305 /* ColorSpaceConversion.swift */; }; 13 | C253827D207681E3008BDB09 /* DependencyNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C253827C207681E3008BDB09 /* DependencyNode.swift */; }; 14 | C2538282207685A5008BDB09 /* HexColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2538281207685A5008BDB09 /* HexColors.swift */; }; 15 | C26572C220786509005650E8 /* CharacterSet+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26572C120786509005650E8 /* CharacterSet+Extension.swift */; }; 16 | C26572C42078652B005650E8 /* Parser+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26572C32078652B005650E8 /* Parser+Extension.swift */; }; 17 | C26572C620786545005650E8 /* Parser+Runes.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26572C520786545005650E8 /* Parser+Runes.swift */; }; 18 | C2756DF320765C8100FF160B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2756DF220765C8100FF160B /* AppDelegate.swift */; }; 19 | C2756DF520765C8100FF160B /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2756DF420765C8100FF160B /* ViewController.swift */; }; 20 | C2756DF720765C8200FF160B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C2756DF620765C8200FF160B /* Assets.xcassets */; }; 21 | C2756DFA20765C8200FF160B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C2756DF820765C8200FF160B /* Main.storyboard */; }; 22 | C2756E1620765CBB00FF160B /* Luminosity.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2756E0320765CBA00FF160B /* Luminosity.swift */; }; 23 | C2756E1720765CBB00FF160B /* RandomColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2756E0420765CBA00FF160B /* RandomColor.swift */; }; 24 | C2756E1820765CBB00FF160B /* ColorDefinition.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2756E0520765CBA00FF160B /* ColorDefinition.swift */; }; 25 | C2756E1920765CBB00FF160B /* Hue.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2756E0620765CBA00FF160B /* Hue.swift */; }; 26 | C2756E1E20765CBB00FF160B /* Curry.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2756E0E20765CBB00FF160B /* Curry.swift */; }; 27 | C2756E1F20765CBB00FF160B /* Runes.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2756E0F20765CBB00FF160B /* Runes.swift */; }; 28 | C2756E2020765CBB00FF160B /* PodLockFileParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2756E1020765CBB00FF160B /* PodLockFileParser.swift */; }; 29 | C2756E2120765CBB00FF160B /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2756E1120765CBB00FF160B /* Parser.swift */; }; 30 | C2756E2920765D4E00FF160B /* RelationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2756E2620765D4E00FF160B /* RelationView.swift */; }; 31 | C2756E2A20765D4E00FF160B /* Array+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2756E2720765D4E00FF160B /* Array+Extension.swift */; }; 32 | C2756E2B20765D4E00FF160B /* RelationItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2756E2820765D4E00FF160B /* RelationItemView.swift */; }; 33 | C2D8BBFD2076A4960093FC8E /* DependencySort.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D8BBFC2076A4960093FC8E /* DependencySort.swift */; }; 34 | C2D8BC002077201E0093FC8E /* PrettyRelation+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D8BBFF2077201E0093FC8E /* PrettyRelation+Extension.swift */; }; 35 | C2D8BC02207720F30093FC8E /* NSPoint+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D8BC01207720F30093FC8E /* NSPoint+Extension.swift */; }; 36 | C2D8BC04207720FD0093FC8E /* NSRect+Dependency.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D8BC03207720FD0093FC8E /* NSRect+Dependency.swift */; }; 37 | C2D8BC06207721230093FC8E /* PrettyRelation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D8BC05207721230093FC8E /* PrettyRelation.swift */; }; 38 | /* End PBXBuildFile section */ 39 | 40 | /* Begin PBXFileReference section */ 41 | C239289220791B980071E305 /* ColorDifference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorDifference.swift; sourceTree = ""; }; 42 | C239289420791D410071E305 /* Color+Difference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Difference.swift"; sourceTree = ""; }; 43 | C239289620791F4C0071E305 /* ColorSpaceConversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorSpaceConversion.swift; sourceTree = ""; }; 44 | C253827C207681E3008BDB09 /* DependencyNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependencyNode.swift; sourceTree = ""; }; 45 | C2538281207685A5008BDB09 /* HexColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HexColors.swift; sourceTree = ""; }; 46 | C26572C120786509005650E8 /* CharacterSet+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CharacterSet+Extension.swift"; sourceTree = ""; }; 47 | C26572C32078652B005650E8 /* Parser+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Parser+Extension.swift"; sourceTree = ""; }; 48 | C26572C520786545005650E8 /* Parser+Runes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Parser+Runes.swift"; sourceTree = ""; }; 49 | C2756DEF20765C8100FF160B /* Pretty.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Pretty.app; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | C2756DF220765C8100FF160B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 51 | C2756DF420765C8100FF160B /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 52 | C2756DF620765C8200FF160B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 53 | C2756DF920765C8200FF160B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 54 | C2756DFB20765C8200FF160B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 55 | C2756DFC20765C8200FF160B /* Pretty.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Pretty.entitlements; sourceTree = ""; }; 56 | C2756E0320765CBA00FF160B /* Luminosity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Luminosity.swift; sourceTree = ""; }; 57 | C2756E0420765CBA00FF160B /* RandomColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RandomColor.swift; sourceTree = ""; }; 58 | C2756E0520765CBA00FF160B /* ColorDefinition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorDefinition.swift; sourceTree = ""; }; 59 | C2756E0620765CBA00FF160B /* Hue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Hue.swift; sourceTree = ""; }; 60 | C2756E0720765CBA00FF160B /* RandomColor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RandomColor.h; sourceTree = ""; }; 61 | C2756E0E20765CBB00FF160B /* Curry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Curry.swift; sourceTree = ""; }; 62 | C2756E0F20765CBB00FF160B /* Runes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Runes.swift; sourceTree = ""; }; 63 | C2756E1020765CBB00FF160B /* PodLockFileParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PodLockFileParser.swift; sourceTree = ""; }; 64 | C2756E1120765CBB00FF160B /* Parser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = ""; }; 65 | C2756E2620765D4E00FF160B /* RelationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RelationView.swift; sourceTree = ""; }; 66 | C2756E2720765D4E00FF160B /* Array+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+Extension.swift"; sourceTree = ""; }; 67 | C2756E2820765D4E00FF160B /* RelationItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RelationItemView.swift; sourceTree = ""; }; 68 | C2D8BBFC2076A4960093FC8E /* DependencySort.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependencySort.swift; sourceTree = ""; }; 69 | C2D8BBFF2077201E0093FC8E /* PrettyRelation+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PrettyRelation+Extension.swift"; sourceTree = ""; }; 70 | C2D8BC01207720F30093FC8E /* NSPoint+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSPoint+Extension.swift"; sourceTree = ""; }; 71 | C2D8BC03207720FD0093FC8E /* NSRect+Dependency.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSRect+Dependency.swift"; sourceTree = ""; }; 72 | C2D8BC05207721230093FC8E /* PrettyRelation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrettyRelation.swift; sourceTree = ""; }; 73 | /* End PBXFileReference section */ 74 | 75 | /* Begin PBXFrameworksBuildPhase section */ 76 | C2756DEC20765C8100FF160B /* Frameworks */ = { 77 | isa = PBXFrameworksBuildPhase; 78 | buildActionMask = 2147483647; 79 | files = ( 80 | ); 81 | runOnlyForDeploymentPostprocessing = 0; 82 | }; 83 | /* End PBXFrameworksBuildPhase section */ 84 | 85 | /* Begin PBXGroup section */ 86 | C253827B207681CA008BDB09 /* Model */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | C253827C207681E3008BDB09 /* DependencyNode.swift */, 90 | C2D8BC05207721230093FC8E /* PrettyRelation.swift */, 91 | ); 92 | path = Model; 93 | sourceTree = ""; 94 | }; 95 | C253827E2076824B008BDB09 /* Utils */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | C2538281207685A5008BDB09 /* HexColors.swift */, 99 | C2D8BBFC2076A4960093FC8E /* DependencySort.swift */, 100 | C239289220791B980071E305 /* ColorDifference.swift */, 101 | C239289620791F4C0071E305 /* ColorSpaceConversion.swift */, 102 | ); 103 | path = Utils; 104 | sourceTree = ""; 105 | }; 106 | C2756DE620765C8100FF160B = { 107 | isa = PBXGroup; 108 | children = ( 109 | C2756DF120765C8100FF160B /* Pretty */, 110 | C2756DF020765C8100FF160B /* Products */, 111 | ); 112 | sourceTree = ""; 113 | }; 114 | C2756DF020765C8100FF160B /* Products */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | C2756DEF20765C8100FF160B /* Pretty.app */, 118 | ); 119 | name = Products; 120 | sourceTree = ""; 121 | }; 122 | C2756DF120765C8100FF160B /* Pretty */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | C2D8BC08207722160093FC8E /* Resource */, 126 | C2D8BBFE20771FF80093FC8E /* Extension */, 127 | C2756E0220765CBA00FF160B /* RandomColors */, 128 | C2756E0D20765CBB00FF160B /* Parser */, 129 | C253827E2076824B008BDB09 /* Utils */, 130 | C253827B207681CA008BDB09 /* Model */, 131 | C2D8BC07207721D90093FC8E /* View */, 132 | C2D8BC09207722260093FC8E /* Controller */, 133 | C2756DF220765C8100FF160B /* AppDelegate.swift */, 134 | C2756DFB20765C8200FF160B /* Info.plist */, 135 | C2756DFC20765C8200FF160B /* Pretty.entitlements */, 136 | ); 137 | path = Pretty; 138 | sourceTree = ""; 139 | }; 140 | C2756E0220765CBA00FF160B /* RandomColors */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | C2756E0320765CBA00FF160B /* Luminosity.swift */, 144 | C2756E0420765CBA00FF160B /* RandomColor.swift */, 145 | C2756E0520765CBA00FF160B /* ColorDefinition.swift */, 146 | C2756E0620765CBA00FF160B /* Hue.swift */, 147 | C2756E0720765CBA00FF160B /* RandomColor.h */, 148 | ); 149 | path = RandomColors; 150 | sourceTree = ""; 151 | }; 152 | C2756E0D20765CBB00FF160B /* Parser */ = { 153 | isa = PBXGroup; 154 | children = ( 155 | C2756E0E20765CBB00FF160B /* Curry.swift */, 156 | C2756E0F20765CBB00FF160B /* Runes.swift */, 157 | C2756E1120765CBB00FF160B /* Parser.swift */, 158 | C2756E1020765CBB00FF160B /* PodLockFileParser.swift */, 159 | C26572C32078652B005650E8 /* Parser+Extension.swift */, 160 | C26572C520786545005650E8 /* Parser+Runes.swift */, 161 | ); 162 | path = Parser; 163 | sourceTree = ""; 164 | }; 165 | C2D8BBFE20771FF80093FC8E /* Extension */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | C2756E2720765D4E00FF160B /* Array+Extension.swift */, 169 | C2D8BBFF2077201E0093FC8E /* PrettyRelation+Extension.swift */, 170 | C2D8BC01207720F30093FC8E /* NSPoint+Extension.swift */, 171 | C2D8BC03207720FD0093FC8E /* NSRect+Dependency.swift */, 172 | C26572C120786509005650E8 /* CharacterSet+Extension.swift */, 173 | C239289420791D410071E305 /* Color+Difference.swift */, 174 | ); 175 | path = Extension; 176 | sourceTree = ""; 177 | }; 178 | C2D8BC07207721D90093FC8E /* View */ = { 179 | isa = PBXGroup; 180 | children = ( 181 | C2756E2820765D4E00FF160B /* RelationItemView.swift */, 182 | C2756E2620765D4E00FF160B /* RelationView.swift */, 183 | ); 184 | path = View; 185 | sourceTree = ""; 186 | }; 187 | C2D8BC08207722160093FC8E /* Resource */ = { 188 | isa = PBXGroup; 189 | children = ( 190 | C2756DF620765C8200FF160B /* Assets.xcassets */, 191 | C2756DF820765C8200FF160B /* Main.storyboard */, 192 | ); 193 | path = Resource; 194 | sourceTree = ""; 195 | }; 196 | C2D8BC09207722260093FC8E /* Controller */ = { 197 | isa = PBXGroup; 198 | children = ( 199 | C2756DF420765C8100FF160B /* ViewController.swift */, 200 | ); 201 | path = Controller; 202 | sourceTree = ""; 203 | }; 204 | /* End PBXGroup section */ 205 | 206 | /* Begin PBXNativeTarget section */ 207 | C2756DEE20765C8100FF160B /* Pretty */ = { 208 | isa = PBXNativeTarget; 209 | buildConfigurationList = C2756DFF20765C8200FF160B /* Build configuration list for PBXNativeTarget "Pretty" */; 210 | buildPhases = ( 211 | C2756DEB20765C8100FF160B /* Sources */, 212 | C2756DEC20765C8100FF160B /* Frameworks */, 213 | C2756DED20765C8100FF160B /* Resources */, 214 | ); 215 | buildRules = ( 216 | ); 217 | dependencies = ( 218 | ); 219 | name = Pretty; 220 | productName = Pretty; 221 | productReference = C2756DEF20765C8100FF160B /* Pretty.app */; 222 | productType = "com.apple.product-type.application"; 223 | }; 224 | /* End PBXNativeTarget section */ 225 | 226 | /* Begin PBXProject section */ 227 | C2756DE720765C8100FF160B /* Project object */ = { 228 | isa = PBXProject; 229 | attributes = { 230 | LastSwiftUpdateCheck = 0930; 231 | LastUpgradeCheck = 0930; 232 | ORGANIZATIONNAME = Octree; 233 | TargetAttributes = { 234 | C2756DEE20765C8100FF160B = { 235 | CreatedOnToolsVersion = 9.3; 236 | }; 237 | }; 238 | }; 239 | buildConfigurationList = C2756DEA20765C8100FF160B /* Build configuration list for PBXProject "Pretty" */; 240 | compatibilityVersion = "Xcode 9.3"; 241 | developmentRegion = en; 242 | hasScannedForEncodings = 0; 243 | knownRegions = ( 244 | en, 245 | Base, 246 | ); 247 | mainGroup = C2756DE620765C8100FF160B; 248 | productRefGroup = C2756DF020765C8100FF160B /* Products */; 249 | projectDirPath = ""; 250 | projectRoot = ""; 251 | targets = ( 252 | C2756DEE20765C8100FF160B /* Pretty */, 253 | ); 254 | }; 255 | /* End PBXProject section */ 256 | 257 | /* Begin PBXResourcesBuildPhase section */ 258 | C2756DED20765C8100FF160B /* Resources */ = { 259 | isa = PBXResourcesBuildPhase; 260 | buildActionMask = 2147483647; 261 | files = ( 262 | C2756DF720765C8200FF160B /* Assets.xcassets in Resources */, 263 | C2756DFA20765C8200FF160B /* Main.storyboard in Resources */, 264 | ); 265 | runOnlyForDeploymentPostprocessing = 0; 266 | }; 267 | /* End PBXResourcesBuildPhase section */ 268 | 269 | /* Begin PBXSourcesBuildPhase section */ 270 | C2756DEB20765C8100FF160B /* Sources */ = { 271 | isa = PBXSourcesBuildPhase; 272 | buildActionMask = 2147483647; 273 | files = ( 274 | C2756E1E20765CBB00FF160B /* Curry.swift in Sources */, 275 | C239289520791D410071E305 /* Color+Difference.swift in Sources */, 276 | C26572C220786509005650E8 /* CharacterSet+Extension.swift in Sources */, 277 | C239289320791B980071E305 /* ColorDifference.swift in Sources */, 278 | C2D8BC06207721230093FC8E /* PrettyRelation.swift in Sources */, 279 | C2538282207685A5008BDB09 /* HexColors.swift in Sources */, 280 | C26572C620786545005650E8 /* Parser+Runes.swift in Sources */, 281 | C2756E1720765CBB00FF160B /* RandomColor.swift in Sources */, 282 | C2D8BC02207720F30093FC8E /* NSPoint+Extension.swift in Sources */, 283 | C253827D207681E3008BDB09 /* DependencyNode.swift in Sources */, 284 | C2756E1820765CBB00FF160B /* ColorDefinition.swift in Sources */, 285 | C26572C42078652B005650E8 /* Parser+Extension.swift in Sources */, 286 | C2756E2B20765D4E00FF160B /* RelationItemView.swift in Sources */, 287 | C2756E2A20765D4E00FF160B /* Array+Extension.swift in Sources */, 288 | C2756E2920765D4E00FF160B /* RelationView.swift in Sources */, 289 | C2756E1F20765CBB00FF160B /* Runes.swift in Sources */, 290 | C2756E1920765CBB00FF160B /* Hue.swift in Sources */, 291 | C2D8BC002077201E0093FC8E /* PrettyRelation+Extension.swift in Sources */, 292 | C239289720791F4C0071E305 /* ColorSpaceConversion.swift in Sources */, 293 | C2756E1620765CBB00FF160B /* Luminosity.swift in Sources */, 294 | C2756E2120765CBB00FF160B /* Parser.swift in Sources */, 295 | C2756DF520765C8100FF160B /* ViewController.swift in Sources */, 296 | C2756DF320765C8100FF160B /* AppDelegate.swift in Sources */, 297 | C2756E2020765CBB00FF160B /* PodLockFileParser.swift in Sources */, 298 | C2D8BBFD2076A4960093FC8E /* DependencySort.swift in Sources */, 299 | C2D8BC04207720FD0093FC8E /* NSRect+Dependency.swift in Sources */, 300 | ); 301 | runOnlyForDeploymentPostprocessing = 0; 302 | }; 303 | /* End PBXSourcesBuildPhase section */ 304 | 305 | /* Begin PBXVariantGroup section */ 306 | C2756DF820765C8200FF160B /* Main.storyboard */ = { 307 | isa = PBXVariantGroup; 308 | children = ( 309 | C2756DF920765C8200FF160B /* Base */, 310 | ); 311 | name = Main.storyboard; 312 | sourceTree = ""; 313 | }; 314 | /* End PBXVariantGroup section */ 315 | 316 | /* Begin XCBuildConfiguration section */ 317 | C2756DFD20765C8200FF160B /* Debug */ = { 318 | isa = XCBuildConfiguration; 319 | buildSettings = { 320 | ALWAYS_SEARCH_USER_PATHS = NO; 321 | CLANG_ANALYZER_NONNULL = YES; 322 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 323 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 324 | CLANG_CXX_LIBRARY = "libc++"; 325 | CLANG_ENABLE_MODULES = YES; 326 | CLANG_ENABLE_OBJC_ARC = YES; 327 | CLANG_ENABLE_OBJC_WEAK = YES; 328 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 329 | CLANG_WARN_BOOL_CONVERSION = YES; 330 | CLANG_WARN_COMMA = YES; 331 | CLANG_WARN_CONSTANT_CONVERSION = YES; 332 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 333 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 334 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 335 | CLANG_WARN_EMPTY_BODY = YES; 336 | CLANG_WARN_ENUM_CONVERSION = YES; 337 | CLANG_WARN_INFINITE_RECURSION = YES; 338 | CLANG_WARN_INT_CONVERSION = YES; 339 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 340 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 341 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 342 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 343 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 344 | CLANG_WARN_STRICT_PROTOTYPES = YES; 345 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 346 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 347 | CLANG_WARN_UNREACHABLE_CODE = YES; 348 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 349 | CODE_SIGN_IDENTITY = "Mac Developer"; 350 | COPY_PHASE_STRIP = NO; 351 | DEBUG_INFORMATION_FORMAT = dwarf; 352 | ENABLE_STRICT_OBJC_MSGSEND = YES; 353 | ENABLE_TESTABILITY = YES; 354 | GCC_C_LANGUAGE_STANDARD = gnu11; 355 | GCC_DYNAMIC_NO_PIC = NO; 356 | GCC_NO_COMMON_BLOCKS = YES; 357 | GCC_OPTIMIZATION_LEVEL = 0; 358 | GCC_PREPROCESSOR_DEFINITIONS = ( 359 | "DEBUG=1", 360 | "$(inherited)", 361 | ); 362 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 363 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 364 | GCC_WARN_UNDECLARED_SELECTOR = YES; 365 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 366 | GCC_WARN_UNUSED_FUNCTION = YES; 367 | GCC_WARN_UNUSED_VARIABLE = YES; 368 | MACOSX_DEPLOYMENT_TARGET = 10.13; 369 | MTL_ENABLE_DEBUG_INFO = YES; 370 | ONLY_ACTIVE_ARCH = YES; 371 | SDKROOT = macosx; 372 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 373 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 374 | }; 375 | name = Debug; 376 | }; 377 | C2756DFE20765C8200FF160B /* Release */ = { 378 | isa = XCBuildConfiguration; 379 | buildSettings = { 380 | ALWAYS_SEARCH_USER_PATHS = NO; 381 | CLANG_ANALYZER_NONNULL = YES; 382 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 383 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 384 | CLANG_CXX_LIBRARY = "libc++"; 385 | CLANG_ENABLE_MODULES = YES; 386 | CLANG_ENABLE_OBJC_ARC = YES; 387 | CLANG_ENABLE_OBJC_WEAK = YES; 388 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 389 | CLANG_WARN_BOOL_CONVERSION = YES; 390 | CLANG_WARN_COMMA = YES; 391 | CLANG_WARN_CONSTANT_CONVERSION = YES; 392 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 393 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 394 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 395 | CLANG_WARN_EMPTY_BODY = YES; 396 | CLANG_WARN_ENUM_CONVERSION = YES; 397 | CLANG_WARN_INFINITE_RECURSION = YES; 398 | CLANG_WARN_INT_CONVERSION = YES; 399 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 400 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 401 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 402 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 403 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 404 | CLANG_WARN_STRICT_PROTOTYPES = YES; 405 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 406 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 407 | CLANG_WARN_UNREACHABLE_CODE = YES; 408 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 409 | CODE_SIGN_IDENTITY = "Mac Developer"; 410 | COPY_PHASE_STRIP = NO; 411 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 412 | ENABLE_NS_ASSERTIONS = NO; 413 | ENABLE_STRICT_OBJC_MSGSEND = YES; 414 | GCC_C_LANGUAGE_STANDARD = gnu11; 415 | GCC_NO_COMMON_BLOCKS = YES; 416 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 417 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 418 | GCC_WARN_UNDECLARED_SELECTOR = YES; 419 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 420 | GCC_WARN_UNUSED_FUNCTION = YES; 421 | GCC_WARN_UNUSED_VARIABLE = YES; 422 | MACOSX_DEPLOYMENT_TARGET = 10.13; 423 | MTL_ENABLE_DEBUG_INFO = NO; 424 | SDKROOT = macosx; 425 | SWIFT_COMPILATION_MODE = wholemodule; 426 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 427 | }; 428 | name = Release; 429 | }; 430 | C2756E0020765C8200FF160B /* Debug */ = { 431 | isa = XCBuildConfiguration; 432 | buildSettings = { 433 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 434 | CODE_SIGN_ENTITLEMENTS = Pretty/Pretty.entitlements; 435 | CODE_SIGN_STYLE = Automatic; 436 | COMBINE_HIDPI_IMAGES = YES; 437 | DEVELOPMENT_TEAM = 7A5GV3DH33; 438 | INFOPLIST_FILE = Pretty/Info.plist; 439 | LD_RUNPATH_SEARCH_PATHS = ( 440 | "$(inherited)", 441 | "@executable_path/../Frameworks", 442 | ); 443 | PRODUCT_BUNDLE_IDENTIFIER = me.octree.Pretty; 444 | PRODUCT_NAME = "$(TARGET_NAME)"; 445 | SWIFT_VERSION = 4.0; 446 | }; 447 | name = Debug; 448 | }; 449 | C2756E0120765C8200FF160B /* Release */ = { 450 | isa = XCBuildConfiguration; 451 | buildSettings = { 452 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 453 | CODE_SIGN_ENTITLEMENTS = Pretty/Pretty.entitlements; 454 | CODE_SIGN_STYLE = Automatic; 455 | COMBINE_HIDPI_IMAGES = YES; 456 | DEVELOPMENT_TEAM = 7A5GV3DH33; 457 | INFOPLIST_FILE = Pretty/Info.plist; 458 | LD_RUNPATH_SEARCH_PATHS = ( 459 | "$(inherited)", 460 | "@executable_path/../Frameworks", 461 | ); 462 | PRODUCT_BUNDLE_IDENTIFIER = me.octree.Pretty; 463 | PRODUCT_NAME = "$(TARGET_NAME)"; 464 | SWIFT_VERSION = 4.0; 465 | }; 466 | name = Release; 467 | }; 468 | /* End XCBuildConfiguration section */ 469 | 470 | /* Begin XCConfigurationList section */ 471 | C2756DEA20765C8100FF160B /* Build configuration list for PBXProject "Pretty" */ = { 472 | isa = XCConfigurationList; 473 | buildConfigurations = ( 474 | C2756DFD20765C8200FF160B /* Debug */, 475 | C2756DFE20765C8200FF160B /* Release */, 476 | ); 477 | defaultConfigurationIsVisible = 0; 478 | defaultConfigurationName = Release; 479 | }; 480 | C2756DFF20765C8200FF160B /* Build configuration list for PBXNativeTarget "Pretty" */ = { 481 | isa = XCConfigurationList; 482 | buildConfigurations = ( 483 | C2756E0020765C8200FF160B /* Debug */, 484 | C2756E0120765C8200FF160B /* Release */, 485 | ); 486 | defaultConfigurationIsVisible = 0; 487 | defaultConfigurationName = Release; 488 | }; 489 | /* End XCConfigurationList section */ 490 | }; 491 | rootObject = C2756DE720765C8100FF160B /* Project object */; 492 | } 493 | -------------------------------------------------------------------------------- /Pretty.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Pretty.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Pretty.xcodeproj/xcuserdata/octree.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /Pretty.xcodeproj/xcuserdata/octree.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Pretty.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Pretty/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MacApp 4 | // 5 | // Created by Octree on 2018/4/5. 6 | // Copyright © 2018年 Octree. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | let OCTOpenFileNotification = "OCTOpenFileNotification" 12 | public var FileName = "" 13 | 14 | @NSApplicationMain 15 | class AppDelegate: NSObject, NSApplicationDelegate { 16 | 17 | func applicationDidFinishLaunching(_ aNotification: Notification) { 18 | // Insert code here to initialize your application 19 | } 20 | 21 | func applicationWillTerminate(_ aNotification: Notification) { 22 | // Insert code here to tear down your application 23 | } 24 | 25 | func application(_ sender: NSApplication, openFile filename: String) -> Bool { 26 | 27 | NotificationCenter.default.post(name:NSNotification.Name(rawValue: OCTOpenFileNotification) , object: filename) 28 | FileName = filename 29 | return true 30 | } 31 | // 32 | // func application(_ sender: NSApplication, openFiles filenames: [String]) { 33 | // 34 | // 35 | // } 36 | 37 | } 38 | 39 | 40 | -------------------------------------------------------------------------------- /Pretty/Controller/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Pretty 4 | // 5 | // Created by Octree on 2018/4/5. 6 | // Copyright © 2018年 Octree. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class ViewController: NSViewController { 12 | 13 | @IBOutlet weak var scrollView: NSScrollView! 14 | 15 | private let relationView = RelationView() 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | scrollView.hasVerticalScroller = true 21 | scrollView.hasHorizontalScroller = true 22 | scrollView.documentView = relationView 23 | 24 | NotificationCenter.default.addObserver(self, selector: #selector(self.handleOpenFile(notification:)), name: NSNotification.Name(rawValue: OCTOpenFileNotification), object: nil) 25 | 26 | if FileName.count > 0 { 27 | 28 | updateRelationView(filename: FileName) 29 | } 30 | } 31 | 32 | override func viewDidLayout() { 33 | super.viewDidLayout() 34 | 35 | guard !relationView.frame.equalTo(CGRect()) else { 36 | return 37 | } 38 | 39 | let size = relationView.prettyRelation.preferredSize 40 | let parentSize = scrollView.frame.size 41 | relationView.frame = CGRect(x: 0, 42 | y: 0, 43 | width: max(size.width, parentSize.width), 44 | height: max(size.height, parentSize.height)) 45 | } 46 | 47 | 48 | @objc func handleOpenFile(notification: Notification) { 49 | 50 | guard let filename = notification.object as? String else { 51 | return 52 | } 53 | 54 | updateRelationView(filename: filename) 55 | } 56 | 57 | func updateRelationView(filename: String) { 58 | 59 | view.window?.title = filename 60 | if filename.hasSuffix(".lock") { 61 | updateWithLockFile(filename: filename) 62 | } else { 63 | updateWithPrettyFile(filename: filename) 64 | } 65 | } 66 | 67 | func updateWithLockFile(filename: String) { 68 | 69 | do { 70 | 71 | let string = try String(contentsOfFile: filename, encoding: .utf8) 72 | 73 | if let (dependency, _) = PodLockFileParser.parse(Substring(string)) { 74 | 75 | relationView.prettyRelation = PrettyRelation(dependency: dependency) 76 | alert(title: "Info", msg: "loaded \(filename)") 77 | } else { 78 | 79 | alert(title: "Error", msg: "Parse Error: Wrong Format") 80 | } 81 | } catch { 82 | 83 | alert(title: "Error", msg: error.localizedDescription) 84 | } 85 | } 86 | 87 | 88 | func updateWithPrettyFile(filename: String) { 89 | 90 | do { 91 | 92 | let url = URL(fileURLWithPath: filename) 93 | let data = try Data(contentsOf: url) 94 | let relation = try JSONDecoder().decode(PrettyRelation.self, from: data) 95 | relationView.prettyRelation = relation 96 | alert(title: "Info", msg: "loaded \(filename)") 97 | } catch { 98 | 99 | alert(title: "Error", msg: error.localizedDescription) 100 | } 101 | } 102 | 103 | 104 | func alert(title: String, msg: String) { 105 | 106 | let alert = NSAlert() 107 | alert.addButton(withTitle: "Ok") 108 | alert.messageText = title 109 | alert.informativeText = msg 110 | alert.alertStyle = .warning 111 | alert.beginSheetModal(for: self.view.window!, completionHandler: nil) 112 | } 113 | 114 | } 115 | 116 | -------------------------------------------------------------------------------- /Pretty/Extension/Array+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+Extension.swift 3 | // MacApp 4 | // 5 | // Created by Octree on 2018/4/5. 6 | // Copyright © 2018年 Octree. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Array { 12 | 13 | func group(_ size: Int) -> [[Element]] { 14 | 15 | let rows = (count + size - 1) / size 16 | var groups = Array<[Element]>(repeating: [Element](), count: rows) 17 | 18 | for (index, elt) in enumerated() { 19 | 20 | let newIndex = index / size 21 | groups[newIndex].append(elt) 22 | } 23 | 24 | return groups 25 | } 26 | } 27 | 28 | 29 | extension Array where Element: Equatable { 30 | 31 | func contains(_ other: [Element]) -> Bool { 32 | 33 | for elt in other { 34 | if !contains(elt) { 35 | 36 | return false 37 | } 38 | } 39 | 40 | return true 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /Pretty/Extension/CharacterSet+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CharacterSet+Extension.swift 3 | // Pretty 4 | // 5 | // Created by Octree on 2018/4/7. 6 | // Copyright © 2018年 Octree. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension CharacterSet { 12 | 13 | func contains(_ c: Character) -> Bool { 14 | 15 | let scalars = String(c).unicodeScalars 16 | guard scalars.count == 1 else { 17 | return false 18 | } 19 | return contains(scalars.first!) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Pretty/Extension/Color+Difference.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+Difference.swift 3 | // Pretty 4 | // 5 | // Created by Octree on 2018/4/7. 6 | // Copyright © 2018年 Octree. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if os(iOS) || os(watchOS) || os(tvOS) 12 | import UIKit 13 | public typealias OCTColor = UIColor 14 | #else 15 | import Cocoa 16 | public typealias OCTColor = NSColor 17 | #endif 18 | 19 | import GLKit 20 | 21 | extension OCTColor { 22 | 23 | 24 | /// 计算与另一个颜色的差异 25 | /// 26 | /// - Parameter other: NSColor Or UIColor 27 | /// - Returns: CIE2000 颜色差异值 28 | func colorDifference(_ other: OCTColor) -> Float { 29 | 30 | let red = UnsafeMutablePointer.allocate(capacity: 1) 31 | let green = UnsafeMutablePointer.allocate(capacity: 1) 32 | let blue = UnsafeMutablePointer.allocate(capacity: 1) 33 | let alpha = UnsafeMutablePointer.allocate(capacity: 1) 34 | 35 | var color = usingColorSpace(.deviceRGB)! 36 | color.getRed(red, green: green, blue: blue, alpha: alpha) 37 | let lab1 = GLKVector3Make(Float(red[0]), Float(green[0]), Float(blue[0])) 38 | 39 | color = other.usingColorSpace(.deviceRGB)! 40 | color.getRed(red, green: green, blue: blue, alpha: alpha) 41 | let lab2 = GLKVector3Make(Float(red[0]), Float(green[0]), Float(blue[0])) 42 | 43 | return CIE2000SquaredColorDifference(1, kC: 1, kH: 1)(lab1, lab2) 44 | } 45 | } 46 | 47 | 48 | -------------------------------------------------------------------------------- /Pretty/Extension/NSPoint+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSPoint+Extension.swift 3 | // Pretty 4 | // 5 | // Created by Octree on 2018/4/6. 6 | // Copyright © 2018年 Octree. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | extension NSPoint { 13 | 14 | func minus(_ other: NSPoint) -> NSPoint { 15 | 16 | return NSPoint(x: x - other.x, y: y - other.y) 17 | } 18 | 19 | func offset(_ p: NSPoint) -> NSPoint { 20 | 21 | return NSPoint(x: x + p.x, y: y + p.y) 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /Pretty/Extension/NSRect+Dependency.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSRect+Dependency.swift 3 | // Pretty 4 | // 5 | // Created by Octree on 2018/4/6. 6 | // Copyright © 2018年 Octree. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | extension NSRect { 13 | 14 | func offset(_ p: NSPoint) -> NSRect { 15 | 16 | return NSRect(origin: origin.offset(p), size: size) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Pretty/Extension/PrettyRelation+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PrettyRelation+Extension.swift 3 | // Pretty 4 | // 5 | // Created by Octree on 2018/4/6. 6 | // Copyright © 2018年 Octree. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | private let kRelationHorizentalSpacing = 30 12 | private let kRelationVerticalSpacing = 100 13 | private let kRelationItemHeight = 28 14 | private let kRelationItemPerRow = 6 15 | private let kRelationViewPadding = 40 16 | 17 | private func widthForItem(_ text: String) -> Int { 18 | 19 | return 10 + 9 * text.count 20 | } 21 | 22 | 23 | private func preferredSize(_ groups: [[String: [String]]]) -> NSSize { 24 | 25 | let rows = groups.count 26 | let height = (kRelationItemHeight + kRelationVerticalSpacing) * rows + kRelationViewPadding * 2 27 | let width = groups.map { elt in 28 | return elt.keys.reduce(2 * kRelationViewPadding) { 29 | $0 + 10 + $1.count * 9 + kRelationHorizentalSpacing 30 | } 31 | }.reduce(0, max) 32 | 33 | return NSSize(width: width, height: height) 34 | } 35 | 36 | private func nodesForGroups(_ groups: [[String: [String]]]) -> [DependencyNode] { 37 | 38 | var nodes = [DependencyNode]() 39 | 40 | for (row, g) in groups.enumerated() { 41 | 42 | var x = kRelationViewPadding 43 | let y = kRelationViewPadding + row * (kRelationItemHeight + kRelationVerticalSpacing) 44 | for elt in g { 45 | 46 | let width = widthForItem(elt.key) 47 | let color = randomColor() 48 | let node = DependencyNode(name: elt.key, 49 | color: color.hex, 50 | frame: NodeFrame(x: x, y: y, width: width, height: kRelationItemHeight), 51 | sons: elt.value) 52 | nodes.append(node) 53 | x += width + kRelationHorizentalSpacing 54 | } 55 | } 56 | 57 | return nodes 58 | } 59 | 60 | extension PrettyRelation { 61 | 62 | convenience init(dependency: [String: [String]]) { 63 | 64 | let groups = Array(groupPodDependency(dependency).reversed()) 65 | // let size = preferredSize(groups) 66 | self.init(nodes: nodesForGroups(groups)) 67 | } 68 | } 69 | 70 | 71 | extension PrettyRelation { 72 | 73 | var preferredSize: CGSize { 74 | 75 | let (width, height) = nodes.reduce((0, 0)) { (result, node) in 76 | 77 | return (max(result.0, node.frame.x + node.frame.width), 78 | max(result.1, node.frame.y + node.frame.height)) 79 | } 80 | return CGSize(width: width + kRelationViewPadding, height: height + kRelationViewPadding) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Pretty/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDocumentTypes 8 | 9 | 10 | CFBundleTypeExtensions 11 | 12 | lock 13 | 14 | CFBundleTypeName 15 | Podfile 16 | CFBundleTypeRole 17 | Viewer 18 | NSDocumentClass 19 | NSDocument 20 | 21 | 22 | CFBundleTypeExtensions 23 | 24 | tree 25 | 26 | CFBundleTypeIconFile 27 | 28 | CFBundleTypeName 29 | pretty 30 | CFBundleTypeRole 31 | Editor 32 | NSDocumentClass 33 | NSDocument 34 | 35 | 36 | CFBundleExecutable 37 | $(EXECUTABLE_NAME) 38 | CFBundleIconFile 39 | 40 | CFBundleIdentifier 41 | $(PRODUCT_BUNDLE_IDENTIFIER) 42 | CFBundleInfoDictionaryVersion 43 | 6.0 44 | CFBundleName 45 | $(PRODUCT_NAME) 46 | CFBundlePackageType 47 | APPL 48 | CFBundleShortVersionString 49 | 1.0 50 | CFBundleVersion 51 | 1 52 | LSMinimumSystemVersion 53 | $(MACOSX_DEPLOYMENT_TARGET) 54 | NSHumanReadableCopyright 55 | Copyright © 2018年 Octree. All rights reserved. 56 | NSMainStoryboardFile 57 | Main 58 | NSPrincipalClass 59 | NSApplication 60 | 61 | 62 | -------------------------------------------------------------------------------- /Pretty/Model/DependencyNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DependencyNode.swift 3 | // Pretty 4 | // 5 | // Created by Octree on 2018/4/6. 6 | // Copyright © 2018年 Octree. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | 12 | struct NodeFrame: Codable { 13 | 14 | var x: Int 15 | var y: Int 16 | var width: Int 17 | var height: Int 18 | } 19 | 20 | extension NodeFrame { 21 | 22 | var rect: NSRect { 23 | return NSRect(x: x, y: y, width: width, height: height) 24 | } 25 | } 26 | 27 | class DependencyNode: Codable { 28 | 29 | var name: String 30 | var color: String 31 | var frame: NodeFrame 32 | var sons: [String] 33 | 34 | init(name: String, color: String, frame: NodeFrame, sons: [String]) { 35 | 36 | self.name = name 37 | self.color = color 38 | self.frame = frame 39 | self.sons = sons 40 | } 41 | } 42 | 43 | 44 | -------------------------------------------------------------------------------- /Pretty/Model/PrettyRelation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RepettyRelation.swift 3 | // Pretty 4 | // 5 | // Created by Octree on 2018/4/6. 6 | // Copyright © 2018年 Octree. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class PrettyRelation: Codable { 12 | 13 | var nodes: [DependencyNode] 14 | 15 | init(nodes: [DependencyNode]) { 16 | 17 | self.nodes = nodes 18 | } 19 | } 20 | 21 | extension PrettyRelation { 22 | 23 | func jsonData() throws -> Data { 24 | 25 | let encoder = JSONEncoder() 26 | do { 27 | return try encoder.encode(self) 28 | } catch { 29 | throw error 30 | } 31 | } 32 | 33 | func jsonString() throws -> String { 34 | 35 | do { 36 | return try String(data: jsonData(), encoding: .utf8)! 37 | } catch { 38 | throw error 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Pretty/Parser/Curry.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Curry.swift 3 | // RxDemo 4 | // 5 | // Created by Octree on 2016/10/28. 6 | // Copyright © 2016年 Octree. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // 2 12 | public func curry(_ f: @escaping (T, U) -> V) -> (T) -> (U) -> V { 13 | 14 | return { x in { y in f(x, y) } } 15 | } 16 | 17 | // 3 18 | public func curry(_ f:@escaping (T, U, V) -> W) -> (T) -> (U) -> (V) -> W { 19 | 20 | return {x in {y in {z in f(x, y, z) }}} 21 | } 22 | 23 | // 4 24 | public func curry(_ f:@escaping (T, U, V, W) -> X) -> (T) -> (U) -> (V) -> (W) -> X { 25 | 26 | return {x in {y in {z in {u in f(x, y, z, u) }}}} 27 | } 28 | 29 | // 5 30 | public func curry(_ f:@escaping (T, U, V, W, X) -> Y) -> (T) -> (U) -> (V) -> (W) -> (X) -> Y { 31 | 32 | return {x in {y in {z in {u in {v in f(x, y, z, u, v) }}}}} 33 | } 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Pretty/Parser/Parser+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Parser+Extension.swift 3 | // Pretty 4 | // 5 | // Created by Octree on 2018/4/7. 6 | // Copyright © 2018年 Octree. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | extension Parser { 13 | 14 | private var _many: Parser<[Result]> { 15 | 16 | return Parser<[Result]> { 17 | input in 18 | var result: [Result] = [] 19 | var remainder = input 20 | 21 | while let (element, newRemainder) = self.parse(remainder) { 22 | 23 | result.append(element) 24 | remainder = newRemainder 25 | } 26 | return (result, remainder) 27 | } 28 | } 29 | 30 | 31 | var many: Parser<[Result]> { 32 | 33 | return curry { [$0] + $1 } <^> self <*> self._many 34 | } 35 | 36 | 37 | func map(_ transform: @escaping (Result) -> T) -> Parser { 38 | 39 | return Parser { 40 | input in 41 | guard let (result, remainder) = self.parse(input) else { 42 | return nil 43 | } 44 | return (transform(result), remainder) 45 | } 46 | } 47 | 48 | func then(lhs: @escaping (Result) -> Parser) -> Parser { 49 | 50 | return Parser { 51 | input in 52 | guard let rt = self.parse(input) else { 53 | return nil 54 | } 55 | 56 | return lhs(rt.0).parse(rt.1) 57 | } 58 | } 59 | 60 | func followed(by other: Parser) -> Parser<(Result, A)> { 61 | return Parser<(Result, A)> { 62 | input in 63 | 64 | guard let (first, reminder) = self.parse(input), 65 | let (second, newReminder) = other.parse(reminder) else { 66 | return nil 67 | } 68 | return ((first, second), newReminder) 69 | } 70 | } 71 | 72 | func or(_ other: Parser) -> Parser { 73 | 74 | return Parser { 75 | input in 76 | return self.parse(input) ?? other.parse(input) 77 | } 78 | } 79 | 80 | var optional: Parser { 81 | 82 | return Parser { 83 | input in 84 | guard let (result, remainder) = self.parse(input) else { 85 | return (nil, input) 86 | } 87 | return (result, remainder) 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Pretty/Parser/Parser+Runes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Parser+Runes.swift 3 | // Pretty 4 | // 5 | // Created by Octree on 2018/4/7. 6 | // Copyright © 2018年 Octree. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /// Functor Operator 13 | /// a -> b -> m a -> m b 14 | /// 15 | /// - Parameters: 16 | /// - lhs: a -> b 17 | /// - rhs: m a 18 | /// - Returns: m b 19 | func <^>(lhs: @escaping (A) -> B, rhs: Parser) -> Parser { 20 | return rhs.map(lhs) 21 | } 22 | 23 | 24 | /// Monad 25 | /// a -> m b -> m a -> m b 26 | /// 27 | /// - Parameters: 28 | /// - lhs: a -> m b 29 | /// - rhs: m a 30 | /// - Returns: m b 31 | func >>-(lhs: Parser, rhs: @escaping (A) -> Parser) -> Parser { 32 | 33 | return lhs.then(lhs: rhs) 34 | } 35 | 36 | 37 | /// applicative 38 | /// m (a -> b) -> m a -> m b 39 | /// 40 | /// - Parameters: 41 | /// - lhs: m (a -> b) 42 | /// - rhs: m a 43 | /// - Returns: m b 44 | func <*>(lhs: Parser<(A) -> B>, rhs: Parser) -> Parser { 45 | 46 | return lhs.followed(by: rhs).map { $0($1) } 47 | } 48 | 49 | 50 | 51 | /// Ignoring Left 52 | /// ma -> mb -> mb 53 | /// 54 | /// - Parameters: 55 | /// - lhs: m a 56 | /// - rhs: m b 57 | /// - Returns: m b 58 | func *>(lhs: Parser, rhs: Parser) -> Parser { 59 | 60 | return curry({ _, 61 | y in y }) <^> lhs <*> rhs 62 | } 63 | 64 | 65 | /// Ignoring Right 66 | /// ma -> mb -> ma 67 | /// 68 | /// - Parameters: 69 | /// - lhs: m a 70 | /// - rhs: m b 71 | /// - Returns: m a 72 | func <*(lhs: Parser, rhs: Parser) -> Parser { 73 | 74 | return curry({ x, _ in x }) <^> lhs <*> rhs 75 | } 76 | 77 | 78 | /// or 79 | /// m a -> m a -> m a 80 | /// 81 | /// - Parameters: 82 | /// - lhs: m a 83 | /// - rhs: m a 84 | /// - Returns: m a 85 | func <|>(lhs: Parser, rhs: Parser) -> Parser { 86 | 87 | return lhs.or(rhs) 88 | } 89 | 90 | -------------------------------------------------------------------------------- /Pretty/Parser/Parser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Parser.swift 3 | // MacApp 4 | // 5 | // Created by Octree on 2018/4/5. 6 | // Copyright © 2018年 Octree. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Parser { 12 | 13 | typealias Stream = Substring 14 | 15 | let parse: (Stream) -> (Result, Stream)? 16 | } 17 | 18 | -------------------------------------------------------------------------------- /Pretty/Parser/PodLockFileParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PodLockFileParser.swift 3 | // 解析 Podfile.lock 的 PODS: 字段,获取 pod 间的依赖关系 4 | // 5 | // Created by Octree on 2018/4/5. 6 | // Copyright © 2018年 Octree. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /// Just parse one character 13 | /// 14 | /// - Parameter condition: condition 15 | /// - Returns: Parser 16 | func character(matching condition: @escaping (Character) -> Bool) -> Parser { 17 | 18 | return Parser(parse: { input in 19 | guard let char = input.first, condition(char) else { 20 | return nil 21 | } 22 | return (char, input.dropFirst()) 23 | }) 24 | } 25 | 26 | 27 | /// parse one specific character 28 | /// 29 | /// - Parameter ch: character 30 | /// - Returns: Parser 31 | func character(_ ch: Character) -> Parser { 32 | 33 | return character { 34 | $0 == ch 35 | } 36 | } 37 | 38 | 39 | /// Parse Specific String 40 | /// 41 | /// - Parameter string: expected string 42 | /// - Returns: Parser 43 | func string(_ string: String) -> Parser { 44 | 45 | return Parser { input in 46 | 47 | guard input.hasPrefix(string) else { 48 | return nil 49 | } 50 | return (string, input.dropFirst(string.count)) 51 | } 52 | } 53 | 54 | func dependencyCombine(name: String, sons: [String]?) -> (String, [String]) { 55 | 56 | return (name, sons ?? []) 57 | } 58 | 59 | /// 冒号 60 | private let colon = character { $0 == ":" } 61 | 62 | /// 空格 63 | private let space = character(" ") 64 | 65 | /// 换行 66 | private let newLine = character("\n") 67 | 68 | /// 缩进 69 | private let indentation = space.followed(by: space) 70 | 71 | /// - 72 | private let hyphon = character("-") 73 | private let quote = character("\"") 74 | 75 | private let leftParent = character("(") 76 | private let rightParent = character(")") 77 | 78 | /// Just Parse `PODS:` 😅 79 | private let pods = string("PODS:\n") 80 | 81 | private let word = character { 82 | !CharacterSet.whitespacesAndNewlines.contains($0) }.many.map { String($0) } 83 | 84 | /// Parse Version Part: `(= 1.2.2)` or `(1.2.3)` or `(whatever)` 85 | private let version = leftParent.followed(by: character { $0 != ")" }.many).followed(by: rightParent) 86 | 87 | 88 | private let item = (indentation *> hyphon *> space *> quote.optional *> word) 89 | <* (space.followed(by: version)).optional <* quote.optional <* colon.optional <* newLine 90 | 91 | private let subItem = indentation *> item 92 | 93 | private let dependencyItem = curry(dependencyCombine) <^> item <*> subItem.many.optional 94 | 95 | private let dependencyItems = dependencyItem.many.map { x -> [String : [String]] in 96 | 97 | var map = [String: [String]]() 98 | 99 | x.forEach { map[$0.0] = $0.1 } 100 | return map 101 | } 102 | 103 | 104 | /// 解析 Podfile.lock 105 | /// 解析成功会返回 [String: [String]] 106 | /// key: Pod Name 107 | /// value: 该 Pod 依赖的其他 Pods 108 | let PodLockFileParser = pods *> dependencyItems 109 | -------------------------------------------------------------------------------- /Pretty/Parser/Runes.swift: -------------------------------------------------------------------------------- 1 | precedencegroup RunesMonadicPrecedenceRight { 2 | associativity: right 3 | lowerThan: LogicalDisjunctionPrecedence 4 | higherThan: AssignmentPrecedence 5 | } 6 | 7 | precedencegroup RunesMonadicPrecedenceLeft { 8 | associativity: left 9 | lowerThan: LogicalDisjunctionPrecedence 10 | higherThan: AssignmentPrecedence 11 | } 12 | 13 | precedencegroup RunesAlternativePrecedence { 14 | associativity: left 15 | higherThan: LogicalConjunctionPrecedence 16 | lowerThan: ComparisonPrecedence 17 | } 18 | 19 | precedencegroup RunesApplicativePrecedence { 20 | associativity: left 21 | higherThan: RunesAlternativePrecedence 22 | lowerThan: NilCoalescingPrecedence 23 | } 24 | 25 | precedencegroup RunesApplicativeSequencePrecedence { 26 | associativity: left 27 | higherThan: RunesApplicativePrecedence 28 | lowerThan: NilCoalescingPrecedence 29 | } 30 | 31 | /** 32 | map a function over a value with context 33 | Expected function type: `(a -> b) -> f a -> f b` 34 | Haskell `infixl 4` 35 | */ 36 | infix operator <^> : RunesApplicativePrecedence 37 | 38 | /** 39 | apply a function with context to a value with context 40 | Expected function type: `f (a -> b) -> f a -> f b` 41 | Haskell `infixl 4` 42 | */ 43 | infix operator <*> : RunesApplicativePrecedence 44 | 45 | /** 46 | sequence actions, discarding right (value of the second argument) 47 | Expected function type: `f a -> f b -> f a` 48 | Haskell `infixl 4` 49 | */ 50 | infix operator <* : RunesApplicativeSequencePrecedence 51 | 52 | /** 53 | sequence actions, discarding left (value of the first argument) 54 | Expected function type: `f a -> f b -> f b` 55 | Haskell `infixl 4` 56 | */ 57 | infix operator *> : RunesApplicativeSequencePrecedence 58 | 59 | /** 60 | an associative binary operation 61 | Expected function type: `f a -> f a -> f a` 62 | Haskell `infixl 3` 63 | */ 64 | infix operator <|> : RunesAlternativePrecedence 65 | 66 | /** 67 | map a function over a value with context and flatten the result 68 | Expected function type: `m a -> (a -> m b) -> m b` 69 | Haskell `infixl 1` 70 | */ 71 | infix operator >>- : RunesMonadicPrecedenceLeft 72 | 73 | /** 74 | map a function over a value with context and flatten the result 75 | Expected function type: `(a -> m b) -> m a -> m b` 76 | Haskell `infixr 1` 77 | */ 78 | infix operator -<< : RunesMonadicPrecedenceRight 79 | 80 | /** 81 | compose two functions that produce results in a context, 82 | from left to right, returning a result in that context 83 | Expected function type: `(a -> m b) -> (b -> m c) -> a -> m c` 84 | Haskell `infixr 1` 85 | */ 86 | infix operator >-> : RunesMonadicPrecedenceRight 87 | 88 | /** 89 | compose two functions that produce results in a context, 90 | from right to left, returning a result in that context 91 | like `>->`, but with the arguments flipped 92 | Expected function type: `(b -> m c) -> (a -> m b) -> a -> m c` 93 | Haskell `infixr 1` 94 | */ 95 | infix operator <-< : RunesMonadicPrecedenceRight 96 | -------------------------------------------------------------------------------- /Pretty/Pretty.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Pretty/RandomColors/ColorDefinition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorDefinition.swift 3 | // RandomColorSwift 4 | // 5 | // Copyright (c) 2016 Wei Wang (http://onevcat.com) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | 25 | import Foundation 26 | 27 | typealias Range = (min: Int, max: Int) 28 | 29 | struct ColorDefinition { 30 | let hueRange: Range? 31 | let lowerBounds: [Range] 32 | 33 | lazy var saturationRange: Range = { 34 | let sMin = self.lowerBounds[0].0 35 | let sMax = self.lowerBounds[self.lowerBounds.count - 1].0 36 | return (sMin, sMax) 37 | }() 38 | 39 | lazy var brightnessRange: Range = { 40 | let bMin = self.lowerBounds[self.lowerBounds.count - 1].1 41 | let bMax = self.lowerBounds[0].1 42 | return (bMin, bMax) 43 | }() 44 | 45 | init(hueRange: Range?, lowerBounds: [Range]) { 46 | self.hueRange = hueRange 47 | self.lowerBounds = lowerBounds 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Pretty/RandomColors/Hue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Hue.swift 3 | // RandomColorSwift 4 | // 5 | // Copyright (c) 2016 Wei Wang (http://onevcat.com) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | 25 | import Foundation 26 | 27 | public enum Hue { 28 | 29 | case monochrome, red, orange, yellow, green, blue, purple, pink 30 | case value(Int) 31 | case random 32 | 33 | public func toInt() -> Int { 34 | switch self { 35 | case .monochrome: return 1 36 | case .red: return 2 37 | case .orange: return 3 38 | case .yellow: return 4 39 | case .green: return 5 40 | case .blue: return 6 41 | case .purple: return 7 42 | case .pink: return 8 43 | case .value(_): return -1 44 | case .random: return 0 45 | } 46 | } 47 | } 48 | 49 | public func == (lhs: Hue, rhs: Hue) -> Bool { 50 | return lhs.toInt() == rhs.toInt() 51 | } 52 | 53 | extension Hue: Hashable { 54 | public var hashValue: Int { 55 | get { 56 | return self.toInt() 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Pretty/RandomColors/Luminosity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Luminosity.swift 3 | // RandomColorSwift 4 | // 5 | // Copyright (c) 2016 Wei Wang (http://onevcat.com) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | 25 | import Foundation 26 | 27 | public enum Luminosity: Int { 28 | case bright, light, dark 29 | case random 30 | } 31 | -------------------------------------------------------------------------------- /Pretty/RandomColors/RandomColor.h: -------------------------------------------------------------------------------- 1 | // 2 | // RandomColor.h 3 | // RandomColor 4 | // 5 | // Copyright (c) 2016 Wei Wang (http://onevcat.com) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | 25 | #import 26 | 27 | //! Project version number for RandomColor. 28 | FOUNDATION_EXPORT double RandomColorVersionNumber; 29 | 30 | //! Project version string for RandomColor. 31 | FOUNDATION_EXPORT const unsigned char RandomColorVersionString[]; 32 | 33 | // In this header, you should import all the public headers of your framework using statements like #import 34 | 35 | 36 | -------------------------------------------------------------------------------- /Pretty/RandomColors/RandomColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RandomColor.swift 3 | // RandomColorSwift 4 | // 5 | // Copyright (c) 2016 Wei Wang (http://onevcat.com) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | 25 | #if os(iOS) 26 | import UIKit 27 | public typealias Color = UIColor 28 | #else 29 | import Cocoa 30 | public typealias Color = NSColor 31 | #endif 32 | 33 | private var colorDictionary: [Hue: ColorDefinition] = [ 34 | .monochrome: ColorDefinition(hueRange: nil, lowerBounds: [(0,0), (100,0)]), 35 | .red: ColorDefinition(hueRange: (-26,18), lowerBounds: [(20,100), (30,92), (40,89), (50,85), (60,78), (70,70), (80,60), (90,55), (100,50)]), 36 | .orange: ColorDefinition(hueRange: (19,46), lowerBounds: [(20,100), (30,93), (40,88), (50,86), (60,85), (70,70), (100,70)]), 37 | .yellow: ColorDefinition(hueRange: (47,62), lowerBounds: [(25,100), (40,94), (50,89), (60,86), (70,84), (80,82), (90,80), (100,75)]), 38 | .green: ColorDefinition(hueRange: (63,178), lowerBounds: [(30,100), (40,90), (50,85), (60,81), (70,74), (80,64), (90,50), (100,40)]), 39 | .blue: ColorDefinition(hueRange: (179,257), lowerBounds: [(20,100), (30,86), (40,80), (50,74), (60,60), (70,52), (80,44), (90,39), (100,35)]), 40 | .purple: ColorDefinition(hueRange: (258, 282), lowerBounds: [(20,100), (30,87), (40,79), (50,70), (60,65), (70,59), (80,52), (90,45), (100,42)]), 41 | .pink: ColorDefinition(hueRange: (283, 334), lowerBounds: [(20,100), (30,90), (40,86), (60,84), (80,80), (90,75), (100,73)]) 42 | ] 43 | 44 | extension Hue { 45 | var range: Range { 46 | switch self { 47 | case .value(let value): return (value, value) 48 | case .random: return (0, 360) 49 | default: 50 | if let colorDefinition = colorDictionary[self] { 51 | return colorDefinition.hueRange ?? (0, 360) 52 | } else { 53 | assert(false, "Unrecgonized Hue enum: \(self).") 54 | return (0, 360) 55 | } 56 | } 57 | } 58 | } 59 | 60 | /** 61 | Generate a single random color with some conditions. 62 | 63 | - parameter hue: Hue of target color. It will be the main property of the generated color. Default is .Random. 64 | - parameter luminosity: Luminosity of target color. It will decide the brightness of generated color. Default is .Random. 65 | 66 | - returns: A random color following input conditions. It will be a `UIColor` object for iOS target, and an `NSColor` object for OSX target. 67 | */ 68 | public func randomColor(hue: Hue = .random, luminosity: Luminosity = .random) -> Color { 69 | 70 | func random(in range: Range) -> Int { 71 | assert(range.max >= range.min, "Max in range should be greater than min") 72 | return Int(arc4random_uniform(UInt32(range.max - range.min))) + range.min 73 | } 74 | 75 | func getColorDefinition(hueValue: Int) -> ColorDefinition { 76 | var hueValue = hueValue 77 | 78 | if hueValue >= 334 && hueValue <= 360 { 79 | hueValue -= 360 80 | } 81 | 82 | let color = colorDictionary.values.filter({ (definition: ColorDefinition) -> Bool in 83 | if let hueRange = definition.hueRange { 84 | return hueValue >= hueRange.min && hueValue <= hueRange.max 85 | } else { 86 | return false 87 | } 88 | }) 89 | 90 | assert(color.count == 1, "There should one and only one color satisfied the filter") 91 | return color.first! 92 | } 93 | 94 | func pickHue(_ hue: Hue) -> Int { 95 | var hueValue = random(in: hue.range) 96 | 97 | // Instead of storing red as two seperate ranges, 98 | // we group them, using negative numbers 99 | if hueValue < 0 { 100 | hueValue = hueValue + 360 101 | } 102 | return hueValue 103 | } 104 | 105 | func pickSaturation(color: ColorDefinition, hue: Hue, luminosity: Luminosity) -> Int { 106 | var color = color 107 | 108 | if luminosity == .random { 109 | return random(in: (0, 100)) 110 | } 111 | 112 | if hue == .monochrome { 113 | return 0 114 | } 115 | 116 | let saturationRange = color.saturationRange 117 | var sMin = saturationRange.min 118 | var sMax = saturationRange.max 119 | 120 | switch luminosity { 121 | case .bright: 122 | sMin = 55 123 | case .dark: 124 | sMin = sMax - 10 125 | case .light: 126 | sMax = 55 127 | default: () 128 | } 129 | 130 | return random(in: (sMin, sMax)) 131 | } 132 | 133 | func pickBrightness(color: ColorDefinition, saturationValue: Int, luminosity: Luminosity) -> Int { 134 | var color = color 135 | 136 | func getMinimumBrightness(saturationValue: Int) -> Int { 137 | var lowerBounds = color.lowerBounds; 138 | for i in 0 ..< lowerBounds.count - 1 { 139 | 140 | let s1 = Float(lowerBounds[i].0) 141 | let v1 = Float(lowerBounds[i].1) 142 | 143 | let s2 = Float(lowerBounds[i+1].0) 144 | let v2 = Float(lowerBounds[i+1].1) 145 | 146 | if Float(saturationValue) >= s1 && Float(saturationValue) <= s2 { 147 | let m = (v2 - v1) / (s2 - s1) 148 | let b = v1 - m * s1 149 | return lroundf(m * Float(saturationValue) + b) 150 | } 151 | } 152 | return 0 153 | } 154 | 155 | var bMin = getMinimumBrightness(saturationValue: saturationValue) 156 | var bMax = 100 157 | 158 | switch luminosity { 159 | case .dark: 160 | bMax = bMin + 20 161 | case .light: 162 | bMin = (bMax + bMin) / 2 163 | case .random: 164 | bMin = 0 165 | bMax = 100 166 | default: () 167 | } 168 | 169 | return random(in: (bMin, bMax)) 170 | } 171 | 172 | 173 | let hueValue = pickHue(hue) 174 | 175 | let color = getColorDefinition(hueValue: hueValue) 176 | 177 | let saturationValue = pickSaturation(color: color, hue: hue, luminosity: luminosity) 178 | let brightnessValue = pickBrightness(color: color, saturationValue: saturationValue, luminosity: luminosity) 179 | 180 | #if os(iOS) 181 | return Color(hue: CGFloat(hueValue) / 360.0, 182 | saturation: CGFloat(saturationValue) / 100.0, 183 | brightness: CGFloat(brightnessValue) / 100.0, 184 | alpha: 1.0) 185 | #else 186 | return Color(deviceHue: CGFloat(hueValue) / 360.0, 187 | saturation: CGFloat(saturationValue) / 100.0, 188 | brightness: CGFloat(brightnessValue) / 100.0, 189 | alpha: 1.0) 190 | #endif 191 | } 192 | 193 | /** 194 | Generate a set of random colors with some conditions. 195 | 196 | - parameter count: The count of how many colors will be generated. 197 | - parameter hue: Hue of target color. It will be the main property of the generated color. Default is .Random. 198 | - parameter luminosity: Luminosity of target color. It will decide the brightness of generated color. Default is .Random. 199 | 200 | - returns: An array of random colors following input conditions. The elements will be `UIColor` objects for iOS target, and `NSColor` objects for OSX target. 201 | */ 202 | public func randomColors(count: Int, hue: Hue = .random, luminosity: Luminosity = .random) -> [Color] { 203 | var colors: [Color] = [] 204 | while (colors.count < count) { 205 | colors.append(randomColor(hue: hue, luminosity: luminosity)) 206 | } 207 | return colors 208 | } 209 | 210 | -------------------------------------------------------------------------------- /Pretty/Resource/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "icon_16x16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "icon_16x16@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "icon_32x32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "icon_32x32@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "icon_128x128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "icon_128x128@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "icon_256x256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "icon_256x256@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "icon_512x512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "icon_512x512@2x.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Pretty/Resource/Assets.xcassets/AppIcon.appiconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/octree/pretty/c5012b2c990337b2f503b3ee343a507f94e99cd4/Pretty/Resource/Assets.xcassets/AppIcon.appiconset/icon_128x128.png -------------------------------------------------------------------------------- /Pretty/Resource/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/octree/pretty/c5012b2c990337b2f503b3ee343a507f94e99cd4/Pretty/Resource/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /Pretty/Resource/Assets.xcassets/AppIcon.appiconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/octree/pretty/c5012b2c990337b2f503b3ee343a507f94e99cd4/Pretty/Resource/Assets.xcassets/AppIcon.appiconset/icon_16x16.png -------------------------------------------------------------------------------- /Pretty/Resource/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/octree/pretty/c5012b2c990337b2f503b3ee343a507f94e99cd4/Pretty/Resource/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /Pretty/Resource/Assets.xcassets/AppIcon.appiconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/octree/pretty/c5012b2c990337b2f503b3ee343a507f94e99cd4/Pretty/Resource/Assets.xcassets/AppIcon.appiconset/icon_256x256.png -------------------------------------------------------------------------------- /Pretty/Resource/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/octree/pretty/c5012b2c990337b2f503b3ee343a507f94e99cd4/Pretty/Resource/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /Pretty/Resource/Assets.xcassets/AppIcon.appiconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/octree/pretty/c5012b2c990337b2f503b3ee343a507f94e99cd4/Pretty/Resource/Assets.xcassets/AppIcon.appiconset/icon_32x32.png -------------------------------------------------------------------------------- /Pretty/Resource/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/octree/pretty/c5012b2c990337b2f503b3ee343a507f94e99cd4/Pretty/Resource/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /Pretty/Resource/Assets.xcassets/AppIcon.appiconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/octree/pretty/c5012b2c990337b2f503b3ee343a507f94e99cd4/Pretty/Resource/Assets.xcassets/AppIcon.appiconset/icon_512x512.png -------------------------------------------------------------------------------- /Pretty/Resource/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/octree/pretty/c5012b2c990337b2f503b3ee343a507f94e99cd4/Pretty/Resource/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /Pretty/Resource/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Pretty/Resource/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | Default 530 | 531 | 532 | 533 | 534 | 535 | 536 | Left to Right 537 | 538 | 539 | 540 | 541 | 542 | 543 | Right to Left 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | Default 555 | 556 | 557 | 558 | 559 | 560 | 561 | Left to Right 562 | 563 | 564 | 565 | 566 | 567 | 568 | Right to Left 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | -------------------------------------------------------------------------------- /Pretty/Utils/ColorDifference.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorDifference.swift 3 | // Pretty 4 | // 5 | // Created by Octree on 2018/4/7. 6 | // Copyright © 2018年 Octree. All rights reserved. 7 | // 8 | 9 | import GLKit 10 | 11 | extension GLKVector3 { 12 | 13 | func unpack() -> (Float, Float, Float) { 14 | 15 | return (x, y, z) 16 | } 17 | } 18 | 19 | private func C(_ a: Float, b: Float) -> Float { 20 | return sqrt(pow(a, 2) + pow(b, 2)) 21 | } 22 | 23 | // From http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE2000.html 24 | public func CIE2000SquaredColorDifference( 25 | _ kL: Float = 1, 26 | kC: Float = 1, 27 | kH: Float = 1 28 | ) -> (_ lab1:GLKVector3, _ lab2:GLKVector3) -> Float { 29 | 30 | return { (lab1:GLKVector3, lab2:GLKVector3) -> Float in 31 | let (L1, a1, b1) = lab1.unpack() 32 | let (L2, a2, b2) = lab2.unpack() 33 | 34 | let ΔLp = L2 - L1 35 | let Lbp = (L1 + L2) / 2 36 | 37 | let (C1, C2) = (C(a1, b: b1), C(a2, b: b2)) 38 | let Cb = (C1 + C2) / 2 39 | 40 | let G = (1 - sqrt(pow(Cb, 7) / (pow(Cb, 7) + pow(25, 7)))) / 2 41 | let ap: (Float) -> Float = { a in 42 | return a * (1 + G) 43 | } 44 | let (a1p, a2p) = (ap(a1), ap(a2)) 45 | 46 | let (C1p, C2p) = (C(a1p, b: b1), C(a2p, b: b2)) 47 | let ΔCp = C2p - C1p 48 | let Cbp = (C1p + C2p) / 2 49 | 50 | let hp: (Float, Float) -> Float = { ap, b in 51 | if ap == 0 && b == 0 { return 0 } 52 | let θ = GLKMathRadiansToDegrees(atan2(b, ap)) 53 | return fmod(θ < 0 ? (θ + 360) : θ, 360) 54 | } 55 | let (h1p, h2p) = (hp(a1p, b1), hp(a2p, b2)) 56 | let Δhabs = abs(h1p - h2p) 57 | let Δhp: Float = { 58 | if (C1p == 0 || C2p == 0) { 59 | return 0 60 | } else if Δhabs <= 180 { 61 | return h2p - h1p 62 | } else if h2p <= h1p { 63 | return h2p - h1p + 360 64 | } else { 65 | return h2p - h1p - 360 66 | } 67 | }() 68 | 69 | let ΔHp = 2 * sqrt(C1p * C2p) * sin(GLKMathDegreesToRadians(Δhp / 2)) 70 | let Hbp: Float = { 71 | if (C1p == 0 || C2p == 0) { 72 | return h1p + h2p 73 | } else if Δhabs > 180 { 74 | return (h1p + h2p + 360) / 2 75 | } else { 76 | return (h1p + h2p) / 2 77 | } 78 | }() 79 | 80 | var T = 1 81 | - 0.17 * cos(GLKMathDegreesToRadians(Hbp - 30)) 82 | + 0.24 * cos(GLKMathDegreesToRadians(2 * Hbp)) 83 | 84 | T = T 85 | + 0.32 * cos(GLKMathDegreesToRadians(3 * Hbp + 6)) 86 | - 0.20 * cos(GLKMathDegreesToRadians(4 * Hbp - 63)) 87 | 88 | let Sl = 1 + (0.015 * pow(Lbp - 50, 2)) / sqrt(20 + pow(Lbp - 50, 2)) 89 | let Sc = 1 + 0.045 * Cbp 90 | let Sh = 1 + 0.015 * Cbp * T 91 | 92 | let Δθ = 30 * exp(-pow((Hbp - 275) / 25, 2)) 93 | let Rc = 2 * sqrt(pow(Cbp, 7) / (pow(Cbp, 7) + pow(25, 7))) 94 | let Rt = -Rc * sin(GLKMathDegreesToRadians(2 * Δθ)) 95 | 96 | let Lterm = ΔLp / (kL * Sl) 97 | let Cterm = ΔCp / (kC * Sc) 98 | let Hterm = ΔHp / (kH * Sh) 99 | return pow(Lterm, 2) + pow(Cterm, 2) + pow(Hterm, 2) + Rt * Cterm * Hterm 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Pretty/Utils/ColorSpaceConversion.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorSpaceConversion.swift 3 | // Pretty 4 | // 5 | // Created by Octree on 2018/4/7. 6 | // Copyright © 2018年 Octree. All rights reserved. 7 | // 8 | 9 | import GLKit 10 | 11 | // From https://github.com/indragiek/DominantColor 12 | 13 | 14 | func RGBToSRGB(_ rgbVector: GLKVector3) -> GLKVector3 { 15 | #if TARGET_OS_IPHONE 16 | // sRGB is the native device color space on iOS, no conversion is required. 17 | return rgbVector 18 | #else 19 | let color = NSColor(deviceRed: CGFloat(rgbVector.x), green: CGFloat(rgbVector.y), blue: CGFloat(rgbVector.z), alpha: 1.0) 20 | let srgbColor = color.usingColorSpace(.sRGB) 21 | return GLKVector3Make(Float(srgbColor?.redComponent ?? 0), 22 | Float(srgbColor?.greenComponent ?? 0), 23 | Float(srgbColor?.blueComponent ?? 0)) 24 | #endif 25 | } 26 | 27 | func SRGBToRGB(_ srgbVector: GLKVector3) -> GLKVector3 { 28 | #if TARGET_OS_IPHONE 29 | // sRGB is the native device color space on iOS, no conversion is required. 30 | return srgbVector 31 | #else 32 | let components = [ CGFloat(srgbVector.x), CGFloat(srgbVector.y), CGFloat(srgbVector.z), 1.0 ] 33 | let srgbColor = NSColor(colorSpace: .sRGB, components: components, count: 4) 34 | let rgbColor = srgbColor.usingColorSpace(.deviceRGB) 35 | return GLKVector3Make(Float(rgbColor?.redComponent ?? 0), 36 | Float(rgbColor?.greenComponent ?? 0), 37 | Float(rgbColor?.blueComponent ?? 0)) 38 | #endif 39 | } 40 | 41 | //// http://en.wikipedia.org/wiki/SRGB#Specification_of_the_transformation 42 | // 43 | func SRGBToLinearSRGB(_ srgbVector: GLKVector3) -> GLKVector3 { 44 | 45 | let f: (Float) -> Float = { c in 46 | if (c <= 0.04045) { 47 | return c / 12.92 48 | } else { 49 | return powf((c + 0.055) / 1.055, 2.4) 50 | } 51 | } 52 | return GLKVector3Make(f(srgbVector.x), f(srgbVector.y), f(srgbVector.z)) 53 | } 54 | 55 | func LinearSRGBToSRGB(_ lSrgbVector: GLKVector3) -> GLKVector3 { 56 | 57 | let f: (Float) -> Float = { c in 58 | if (c <= 0.0031308) { 59 | return c * 12.92 60 | } else { 61 | return (1.055 * powf(c, 1.0 / 2.4)) - 0.055 62 | } 63 | } 64 | 65 | return GLKVector3Make(f(lSrgbVector.x), f(lSrgbVector.y), f(lSrgbVector.z)) 66 | } 67 | 68 | // mark: XYZ (CIE 1931) 69 | //// http://en.wikipedia.org/wiki/CIE_1931_color_space#Construction_of_the_CIE_XYZ_color_space_from_the_Wright.E2.80.93Guild_data 70 | 71 | private let LinearSRGBToXYZMatrix = GLKMatrix3Make(0.4124, 0.2126, 0.0193, 0.3576, 0.7152, 0.1192, 0.1805, 0.0722, 0.9505) 72 | 73 | func LinearSRGBToXYZ(_ linearSrgbVector: GLKVector3) -> GLKVector3 { 74 | 75 | let unscaledXYZVector = GLKMatrix3MultiplyVector3(LinearSRGBToXYZMatrix, linearSrgbVector) 76 | return GLKVector3MultiplyScalar(unscaledXYZVector, 100) 77 | } 78 | 79 | private let XYZToLinearSRGBMatrix = GLKMatrix3Make(3.2406, -0.9689, 0.0557, -1.5372, 1.8758, -0.2040, -0.4986, 0.0415, 1.0570) 80 | 81 | func XYZToLinearSRGB(_ xyzVector: GLKVector3) -> GLKVector3 { 82 | 83 | let scaledXYZVector = GLKVector3DivideScalar(xyzVector, 100) 84 | return GLKMatrix3MultiplyVector3(XYZToLinearSRGBMatrix, scaledXYZVector) 85 | } 86 | 87 | // MARK: LAB 88 | //// http://en.wikipedia.org/wiki/Lab_color_space#CIELAB-CIEXYZ_conversions 89 | 90 | func XYZToLAB(_ xyzVector: GLKVector3, tristimulus: GLKVector3) -> GLKVector3 { 91 | 92 | let f: (Float) -> Float = { t in 93 | 94 | if (t > powf(6 / 29, 3)) { 95 | return powf(t, 1 / 3) 96 | } else { 97 | return ((1 / 3) * powf(29 / 6, 2) * t) + (4 / 29) 98 | } 99 | } 100 | let fx = f(xyzVector.x / tristimulus.x) 101 | let fy = f(xyzVector.y / tristimulus.y) 102 | let fz = f(xyzVector.z / tristimulus.z) 103 | 104 | let l = (116 * fy) - 16 105 | let a = 500 * (fx - fy) 106 | let b = 200 * (fy - fz) 107 | 108 | return GLKVector3Make(l, a, b) 109 | } 110 | 111 | func LABToXYZ(_ labVector: GLKVector3, tristimulus: GLKVector3) -> GLKVector3 { 112 | let f: (Float) -> Float = { t in 113 | if (t > (6 / 29)) { 114 | return powf(t, 3) 115 | } else { 116 | return 3 * powf(6 / 29, 2) * (t - (4 / 29)) 117 | } 118 | } 119 | let c = (1 / 116) * (labVector.x + 16) 120 | 121 | let y = tristimulus.y * f(c) 122 | let x = tristimulus.x * f(c + ((1 / 500) * labVector.y)) 123 | let z = tristimulus.z * f(c - ((1 / 200) * labVector.z)) 124 | 125 | return GLKVector3Make(x, y, z) 126 | } 127 | 128 | //#pragma mark - Public 129 | // 130 | //// From http://www.easyrgb.com/index.php?X=MATH&H=15#text15 131 | let D65Tristimulus = GLKVector3Make(95.047, 100, 108.883) 132 | 133 | func RGBToLAB(rgbVector: GLKVector3) -> GLKVector3 { 134 | 135 | let srgbVector = RGBToSRGB(rgbVector) 136 | let lSrgbVector = SRGBToLinearSRGB(srgbVector) 137 | let xyzVector = LinearSRGBToXYZ(lSrgbVector) 138 | return XYZToLAB(xyzVector, tristimulus: D65Tristimulus) 139 | } 140 | 141 | func LABToRGB(labVector: GLKVector3) -> GLKVector3 { 142 | 143 | let xyzVector = LABToXYZ(labVector, tristimulus: D65Tristimulus) 144 | let lSrgbVector = XYZToLinearSRGB(xyzVector) 145 | let srgbVector = LinearSRGBToSRGB(lSrgbVector) 146 | return SRGBToRGB(srgbVector) 147 | } 148 | 149 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /Pretty/Utils/DependencySort.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DependencySort.swift 3 | // Pretty 4 | // 5 | // Created by Octree on 2018/4/6. 6 | // Copyright © 2018年 Octree. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /// 哈哈哈,帮助这些 lib 找爸爸 13 | /// 14 | /// - Parameter dependency: 依赖 [name: Sons] 15 | /// - Returns: [name: dads] 16 | private func whoIsYourDad(_ dependency: [String: [String]]) -> [String: [String]] { 17 | 18 | var dadsMap = [String: [String]]() 19 | for (key, sons) in dependency { 20 | 21 | for name in sons { 22 | 23 | var parents = dadsMap[name] ?? [] 24 | parents.append(key) 25 | dadsMap[name] = parents 26 | } 27 | } 28 | 29 | return dadsMap 30 | } 31 | 32 | 33 | /// 根据 lib 的 depth 进行分组 34 | /// 35 | /// - Parameter dependency: pod dependency 36 | /// - Returns: grouped dependency 37 | func groupPodDependency(_ dependency: [String: [String]]) -> [[String: [String]]] { 38 | 39 | let reversed = whoIsYourDad(dependency) 40 | var names = Set(dependency.keys) 41 | 42 | var lastDepthNames = [String]() 43 | var groups = [[String: [String]]]() 44 | while names.count > 0 { 45 | 46 | var group = [String: [String]]() 47 | let copyedNames = names 48 | for name in copyedNames { 49 | if lastDepthNames.contains(reversed[name] ?? []) { 50 | 51 | names.remove(name) 52 | group[name] = dependency[name] 53 | } 54 | } 55 | lastDepthNames.append(contentsOf: group.keys) 56 | groups.append(group) 57 | } 58 | 59 | return groups 60 | } 61 | -------------------------------------------------------------------------------- /Pretty/Utils/HexColors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HexColors.swift 3 | // 4 | // Created by Marius Landwehr on 25.12.16. 5 | // The MIT License (MIT) 6 | // Copyright (c) 2016 Marius Landwehr marius.landwehr@gmail.com 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | #if os(iOS) || os(watchOS) || os(tvOS) 15 | import UIKit 16 | public typealias HexColor = UIColor 17 | #else 18 | import Cocoa 19 | public typealias HexColor = NSColor 20 | #endif 21 | 22 | 23 | public extension HexColor { 24 | typealias Hex = String 25 | 26 | convenience init?(_ hex: Hex, alpha: CGFloat? = nil) { 27 | 28 | guard let hexType = Type(from: hex), let components = hexType.components() else { 29 | return nil 30 | } 31 | 32 | #if os(iOS) || os(watchOS) || os(tvOS) 33 | self.init(red: components.red, green: components.green, blue: components.blue, alpha: alpha ?? components.alpha) 34 | #else 35 | self.init(calibratedRed: components.red, green: components.green, blue: components.blue, alpha: alpha ?? components.alpha) 36 | #endif 37 | } 38 | 39 | /// The string hex value representation of the current color 40 | var hex: Hex { 41 | var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0, rgb: Int 42 | getRed(&r, green: &g, blue: &b, alpha: &a) 43 | 44 | if a == 1 { // no alpha value set, we are returning the short version 45 | rgb = (Int)(r*255)<<16 | (Int)(g*255)<<8 | (Int)(b*255)<<0 46 | return String(format: "#%06x", rgb) 47 | } else { 48 | rgb = (Int)(r*255)<<24 | (Int)(g*255)<<16 | (Int)(b*255)<<8 | (Int)(a*255)<<0 49 | return String(format: "#%08x", rgb) 50 | } 51 | } 52 | 53 | private enum `Type` { 54 | 55 | case RGBshort(rgb: Hex) 56 | case RGBshortAlpha(rgba: Hex) 57 | case RGB(rgb: Hex) 58 | case RGBA(rgba: Hex) 59 | 60 | init?(from hex: Hex) { 61 | 62 | var hexString = hex 63 | hexString.removeHashIfNecessary() 64 | 65 | guard let t = Type.transform(hex: hexString) else { 66 | return nil 67 | } 68 | 69 | self = t 70 | } 71 | 72 | static func transform(hex string: Hex) -> Type? { 73 | switch string.count { 74 | case 3: 75 | return .RGBshort(rgb: string) 76 | case 4: 77 | return .RGBshortAlpha(rgba: string) 78 | case 6: 79 | return .RGB(rgb: string) 80 | case 8: 81 | return .RGBA(rgba: string) 82 | default: 83 | return nil 84 | } 85 | } 86 | 87 | var value: Hex { 88 | switch self { 89 | case .RGBshort(let rgb): 90 | return rgb 91 | case .RGBshortAlpha(let rgba): 92 | return rgba 93 | case .RGB(let rgb): 94 | return rgb 95 | case .RGBA(let rgba): 96 | return rgba 97 | } 98 | } 99 | 100 | typealias rgbComponents = (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) 101 | func components() -> rgbComponents? { 102 | 103 | var hexValue: UInt32 = 0 104 | guard Scanner(string: value).scanHexInt32(&hexValue) else { 105 | return nil 106 | } 107 | 108 | let r, g, b, a, divisor: CGFloat 109 | 110 | switch self { 111 | case .RGBshort(_): 112 | divisor = 15 113 | r = CGFloat((hexValue & 0xF00) >> 8) / divisor 114 | g = CGFloat((hexValue & 0x0F0) >> 4) / divisor 115 | b = CGFloat( hexValue & 0x00F) / divisor 116 | a = 1 117 | case .RGBshortAlpha(_): 118 | divisor = 15 119 | r = CGFloat((hexValue & 0xF000) >> 12) / divisor 120 | g = CGFloat((hexValue & 0x0F00) >> 8) / divisor 121 | b = CGFloat((hexValue & 0x00F0) >> 4) / divisor 122 | a = CGFloat( hexValue & 0x000F) / divisor 123 | case .RGB(_): 124 | divisor = 255 125 | r = CGFloat((hexValue & 0xFF0000) >> 16) / divisor 126 | g = CGFloat((hexValue & 0x00FF00) >> 8) / divisor 127 | b = CGFloat( hexValue & 0x0000FF) / divisor 128 | a = 1 129 | case .RGBA(_): 130 | divisor = 255 131 | r = CGFloat((hexValue & 0xFF000000) >> 24) / divisor 132 | g = CGFloat((hexValue & 0x00FF0000) >> 16) / divisor 133 | b = CGFloat((hexValue & 0x0000FF00) >> 8) / divisor 134 | a = CGFloat( hexValue & 0x000000FF) / divisor 135 | } 136 | 137 | return (red: r, green: g, blue: b, alpha: a) 138 | } 139 | } 140 | } 141 | 142 | private extension String { 143 | 144 | mutating func removeHashIfNecessary() { 145 | if hasPrefix("#") { 146 | self = replacingOccurrences(of: "#", with: "") 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /Pretty/View/RelationItemView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RelationItemView.swift 3 | // MacApp 4 | // 5 | // Created by Octree on 2018/4/5. 6 | // Copyright © 2018年 Octree. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class RelationItemView: NSView { 12 | 13 | var text = "" { 14 | didSet { 15 | label.stringValue = text 16 | } 17 | } 18 | private(set) var label: NSTextField = { 19 | let textfield = NSTextField() 20 | textfield.isEditable = false 21 | textfield.textColor = .white 22 | textfield.alignment = .center 23 | textfield.isBordered = false 24 | return textfield 25 | }() 26 | 27 | var backgroundColor: NSColor? { 28 | didSet { 29 | layer?.backgroundColor = backgroundColor?.cgColor 30 | label.backgroundColor = backgroundColor 31 | } 32 | } 33 | 34 | init(frame frameRect: NSRect, text: String) { 35 | super.init(frame: frameRect) 36 | self.text = text 37 | addSubview(label) 38 | label.stringValue = text 39 | wantsLayer = true 40 | layer?.cornerRadius = 10 41 | layer?.masksToBounds = true 42 | } 43 | 44 | required init?(coder decoder: NSCoder) { 45 | fatalError("init(coder:) has not been implemented") 46 | } 47 | 48 | override func draw(_ dirtyRect: NSRect) { 49 | super.draw(dirtyRect) 50 | } 51 | 52 | override func layout() { 53 | super.layout() 54 | label.frame = bounds.insetBy(dx: 5, dy: 5) 55 | } 56 | 57 | var center: NSPoint { 58 | 59 | return NSPoint(x: frame.origin.x + frame.size.width / 2, 60 | y: frame.origin.y + frame.size.height / 2) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Pretty/View/RelationView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RelationView.swift 3 | // MacApp 4 | // 5 | // Created by Octree on 2018/4/5. 6 | // Copyright © 2018年 Octree. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | 12 | class RelationView: NSView { 13 | 14 | // MARK: Accessor 15 | 16 | /// "parent|daughter": Layer 17 | private var lineMap = [String: CAShapeLayer]() 18 | private var itemMap = [String: RelationItemView]() 19 | private var nodeMap = [String: DependencyNode]() 20 | private var currentDraggingItem: RelationItemView? = nil 21 | 22 | private var lastDragPosition = NSPoint(x: 0, y: 0) 23 | 24 | var prettyRelation = PrettyRelation(nodes: []) { 25 | 26 | didSet { 27 | setUp() 28 | } 29 | } 30 | 31 | // MARK: Life Cycle 32 | 33 | init() { 34 | super.init(frame: NSRect()) 35 | } 36 | 37 | required init?(coder decoder: NSCoder) { 38 | super.init(coder: decoder) 39 | } 40 | 41 | 42 | // MARK: Public Method 43 | 44 | override func mouseDown(with event: NSEvent) { 45 | 46 | let position = convert(event.locationInWindow, from: nil) 47 | 48 | if let item = findItemLocate(in: position) { 49 | 50 | currentDraggingItem = item 51 | lastDragPosition = position 52 | } 53 | } 54 | 55 | override func mouseDragged(with event: NSEvent) { 56 | 57 | if let item = currentDraggingItem { 58 | 59 | let position = convert(event.locationInWindow, from: nil) 60 | item.frame = item.frame.offset(position.minus(lastDragPosition)) 61 | 62 | lastDragPosition = position 63 | updateLine(relate: item.text) 64 | } 65 | } 66 | 67 | override func mouseUp(with event: NSEvent) { 68 | 69 | guard let item = currentDraggingItem else { 70 | return 71 | } 72 | 73 | if let node = nodeMap[item.text] { 74 | 75 | node.frame = NodeFrame(x: Int(item.frame.origin.x), 76 | y: Int(item.frame.origin.y), 77 | width: Int(item.frame.size.width), 78 | height: Int(item.frame.size.height)) 79 | } 80 | 81 | currentDraggingItem = nil 82 | updateLine(relate: item.text) 83 | } 84 | 85 | 86 | // MARK: Private Method 87 | 88 | private func setUp() { 89 | 90 | clear() 91 | let superSize = superview?.frame.size ?? CGSize() 92 | 93 | let size = prettyRelation.preferredSize 94 | 95 | frame = NSRect(x: 0, y: 0, width: max(size.width, superSize.width), 96 | height: max(superSize.height, size.height)) 97 | 98 | wantsLayer = true 99 | layer?.backgroundColor = NSColor.white.cgColor 100 | 101 | setupItems() 102 | setupLines() 103 | 104 | lineMap.forEach { 105 | layer?.addSublayer($0.1) 106 | } 107 | 108 | itemMap.forEach { 109 | addSubview($0.1) 110 | } 111 | } 112 | 113 | 114 | /// 创建 RelationItemViews 115 | private func setupItems() { 116 | 117 | prettyRelation.nodes.forEach { node in 118 | 119 | let item = RelationItemView(frame: node.frame.rect, text: node.name) 120 | let color = NSColor(node.color) 121 | item.backgroundColor = color 122 | 123 | if (color?.colorDifference(.white) ?? 0) < 0.1 { 124 | 125 | item.label.textColor = NSColor(white: 0.2, alpha: 1.0) 126 | } 127 | 128 | itemMap[node.name] = item 129 | nodeMap[node.name] = node 130 | } 131 | } 132 | 133 | 134 | /// 创建 Node 之间的连接线 135 | private func setupLines() { 136 | 137 | prettyRelation.nodes.forEach { parent in 138 | 139 | let color = NSColor(parent.color) 140 | parent.sons.forEach { son in 141 | 142 | let key = parent.name + "|" + son 143 | let shapeLayer = CAShapeLayer() 144 | shapeLayer.fillColor = NSColor.clear.cgColor 145 | shapeLayer.strokeColor = color?.cgColor 146 | shapeLayer.lineWidth = 2 147 | shapeLayer.path = linePath(parent: parent.name, son: son) 148 | lineMap[key] = shapeLayer 149 | } 150 | } 151 | } 152 | 153 | private func clear() { 154 | 155 | lineMap.removeAll() 156 | itemMap.removeAll() 157 | nodeMap.removeAll() 158 | subviews.forEach { $0.removeFromSuperview() } 159 | layer?.sublayers?.forEach { $0.removeFromSuperlayer() } 160 | } 161 | 162 | 163 | /// 根据 Position 找到对应位置的 RelationItemView 164 | /// 165 | /// - Parameter position: NSPoint 166 | /// - Returns: RelationItemView or nil 167 | private func findItemLocate(in position: NSPoint) -> RelationItemView? { 168 | 169 | for (_, item) in itemMap { 170 | 171 | if item.frame.contains(position) { 172 | return item 173 | } 174 | } 175 | 176 | return nil 177 | } 178 | 179 | 180 | /// 根据父节点和子节点的名称创建连接线的 Path 181 | /// 182 | /// - Parameters: 183 | /// - parent: 父节点名称 184 | /// - son: 子节点名称 185 | /// - Returns: CGPath 186 | private func linePath(parent: String, son: String) -> CGPath { 187 | 188 | let parentItem = itemMap[parent]! 189 | let sonItem = itemMap[son]! 190 | 191 | let path = CGMutablePath() 192 | path.move(to: parentItem.center) 193 | path.addLine(to: sonItem.center) 194 | return path 195 | } 196 | 197 | 198 | /// 更新和某个节点相关的线 199 | /// 200 | /// - Parameter name: 节点名称 201 | private func updateLine(relate name: String) { 202 | 203 | for (key, value) in lineMap { 204 | 205 | let components = key.components(separatedBy: "|") 206 | if components.contains(name) { 207 | 208 | value.path = linePath(parent: components.first!, son: components.last!) 209 | 210 | if components.first == currentDraggingItem?.text { 211 | 212 | value.strokeColor = NSColor.red.cgColor 213 | } else if (components.last == currentDraggingItem?.text) { 214 | 215 | value.strokeColor = NSColor.blue.cgColor 216 | } else { 217 | value.strokeColor = itemMap[components.first!]?.backgroundColor?.cgColor 218 | } 219 | } 220 | } 221 | 222 | } 223 | 224 | } 225 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pretty 2 | 3 | 一个根据 Podfile.lock 生成依赖图的 Mac App。 4 | 5 | 6 | > 和节点颜色相同的线连接子节点。 7 | 8 | 9 | > 被拖拽的节点,红色连接子节点,蓝色连接父节点。 10 | 11 | ## Demo 12 | 13 | ![image](./Images/dependency.png) 14 | 15 | 16 | 17 | ![demo.gif](./Images/demo.gif) 18 | 19 | ## How To Use 20 | 21 | > Dowload & Build 22 | 23 | 24 | ## TODO 25 | 26 | - [x] 拖拽节点 27 | - [x] 拖拽的节点高亮 28 | - [x] 解析 Podfile.lock 29 | - [x] 根据节点背景颜色,使用合适的字体颜色 30 | - [x] 根据节点深度分组 31 | - [ ] 学习 `macos` 开发 32 | - [ ] 保存依赖图文件 33 | - [ ] 打开依赖图文件 34 | 35 | ## License 36 | 37 | Pretty is released under the MIT license. See LICENSE for details.% 38 | --------------------------------------------------------------------------------