├── .gitignore ├── Demos ├── DSFActionBar Demo │ ├── DSFActionBar Demo.xcodeproj │ │ ├── project.pbxproj │ │ └── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── DSFActionBar Demo │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ ├── AccentColor.colorset │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ │ └── Main.storyboard │ │ ├── DSFActionBar_Demo.entitlements │ │ ├── Info.plist │ │ └── ViewController.swift │ └── Simple Demo │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── KeychainTabsBackgroundColor.colorset │ │ │ └── Contents.json │ │ └── SafariTabsBackgroundColor.colorset │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ └── Main.storyboard │ │ ├── Info.plist │ │ ├── SafariStyleActionBarWindowController.swift │ │ ├── SafariStyleActionBarWindowController.xib │ │ ├── Simple_Demo.entitlements │ │ └── ViewController.swift └── DSFActionBar Objc Demo │ ├── DSFActionBar Objc Demo.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── swiftpm │ │ └── Package.resolved │ └── DSFActionBar Objc Demo │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ └── MainMenu.xib │ ├── DSFActionBar_Objc_Demo.entitlements │ ├── Info.plist │ └── main.m ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── DSFActionBar │ ├── DSFActionBar+protocols.swift │ ├── DSFActionBar.swift │ ├── DSFActionTabBar.swift │ └── private │ ├── DSFActionBar+private.swift │ ├── DSFActionBarButton.swift │ ├── DraggingStackView.swift │ └── Utilities.swift └── Tests ├── DSFActionBarTests ├── DSFActionBarTests.swift └── XCTestManifests.swift └── LinuxMain.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | .swiftpm 7 | Demos/DSFActionBar Objc Demo/DSFActionBar Objc Demo.xcodeproj/project.xcworkspace/xcshareddata 8 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Demo/DSFActionBar Demo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2325287725A6A4CB00816037 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2325287625A6A4CB00816037 /* AppDelegate.swift */; }; 11 | 2325287925A6A4CB00816037 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2325287825A6A4CB00816037 /* ViewController.swift */; }; 12 | 2325287B25A6A4CC00816037 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2325287A25A6A4CC00816037 /* Assets.xcassets */; }; 13 | 2325287E25A6A4CC00816037 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2325287C25A6A4CC00816037 /* Main.storyboard */; }; 14 | 2325288B25A6A4E900816037 /* DSFActionBar in Frameworks */ = {isa = PBXBuildFile; productRef = 2325288A25A6A4E900816037 /* DSFActionBar */; }; 15 | 232681CD25A7BDFD002E2A1C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 232681CC25A7BDFD002E2A1C /* AppDelegate.swift */; }; 16 | 232681CF25A7BDFD002E2A1C /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 232681CE25A7BDFD002E2A1C /* ViewController.swift */; }; 17 | 232681D125A7BDFE002E2A1C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 232681D025A7BDFE002E2A1C /* Assets.xcassets */; }; 18 | 232681D425A7BDFE002E2A1C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 232681D225A7BDFE002E2A1C /* Main.storyboard */; }; 19 | 232681DD25A7BE20002E2A1C /* DSFActionBar in Frameworks */ = {isa = PBXBuildFile; productRef = 232681DC25A7BE20002E2A1C /* DSFActionBar */; }; 20 | 23F1282625A834CC0003CABE /* SafariStyleActionBarWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23F1282425A834CC0003CABE /* SafariStyleActionBarWindowController.swift */; }; 21 | 23F1282725A834CC0003CABE /* SafariStyleActionBarWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 23F1282525A834CC0003CABE /* SafariStyleActionBarWindowController.xib */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXFileReference section */ 25 | 2325287325A6A4CB00816037 /* DSFActionBar Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "DSFActionBar Demo.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 26 | 2325287625A6A4CB00816037 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 27 | 2325287825A6A4CB00816037 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 28 | 2325287A25A6A4CC00816037 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 29 | 2325287D25A6A4CC00816037 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 30 | 2325287F25A6A4CC00816037 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 31 | 2325288025A6A4CC00816037 /* DSFActionBar_Demo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DSFActionBar_Demo.entitlements; sourceTree = ""; }; 32 | 2325288725A6A4D500816037 /* DSFActionBar */ = {isa = PBXFileReference; lastKnownFileType = folder; name = DSFActionBar; path = ../..; sourceTree = ""; }; 33 | 232681CA25A7BDFD002E2A1C /* Simple Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Simple Demo.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | 232681CC25A7BDFD002E2A1C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 35 | 232681CE25A7BDFD002E2A1C /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 36 | 232681D025A7BDFE002E2A1C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 37 | 232681D325A7BDFE002E2A1C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 38 | 232681D525A7BDFE002E2A1C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 39 | 232681D625A7BDFE002E2A1C /* Simple_Demo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Simple_Demo.entitlements; sourceTree = ""; }; 40 | 23F1282425A834CC0003CABE /* SafariStyleActionBarWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariStyleActionBarWindowController.swift; sourceTree = ""; }; 41 | 23F1282525A834CC0003CABE /* SafariStyleActionBarWindowController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SafariStyleActionBarWindowController.xib; sourceTree = ""; }; 42 | /* End PBXFileReference section */ 43 | 44 | /* Begin PBXFrameworksBuildPhase section */ 45 | 2325287025A6A4CB00816037 /* Frameworks */ = { 46 | isa = PBXFrameworksBuildPhase; 47 | buildActionMask = 2147483647; 48 | files = ( 49 | 2325288B25A6A4E900816037 /* DSFActionBar in Frameworks */, 50 | ); 51 | runOnlyForDeploymentPostprocessing = 0; 52 | }; 53 | 232681C725A7BDFD002E2A1C /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | 232681DD25A7BE20002E2A1C /* DSFActionBar in Frameworks */, 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | 2325286A25A6A4CB00816037 = { 65 | isa = PBXGroup; 66 | children = ( 67 | 2325288725A6A4D500816037 /* DSFActionBar */, 68 | 2325287525A6A4CB00816037 /* DSFActionBar Demo */, 69 | 232681CB25A7BDFD002E2A1C /* Simple Demo */, 70 | 2325287425A6A4CB00816037 /* Products */, 71 | 2325288925A6A4E900816037 /* Frameworks */, 72 | ); 73 | sourceTree = ""; 74 | }; 75 | 2325287425A6A4CB00816037 /* Products */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 2325287325A6A4CB00816037 /* DSFActionBar Demo.app */, 79 | 232681CA25A7BDFD002E2A1C /* Simple Demo.app */, 80 | ); 81 | name = Products; 82 | sourceTree = ""; 83 | }; 84 | 2325287525A6A4CB00816037 /* DSFActionBar Demo */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 2325287625A6A4CB00816037 /* AppDelegate.swift */, 88 | 2325287825A6A4CB00816037 /* ViewController.swift */, 89 | 2325287A25A6A4CC00816037 /* Assets.xcassets */, 90 | 2325287C25A6A4CC00816037 /* Main.storyboard */, 91 | 2325287F25A6A4CC00816037 /* Info.plist */, 92 | 2325288025A6A4CC00816037 /* DSFActionBar_Demo.entitlements */, 93 | ); 94 | path = "DSFActionBar Demo"; 95 | sourceTree = ""; 96 | }; 97 | 2325288925A6A4E900816037 /* Frameworks */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | ); 101 | name = Frameworks; 102 | sourceTree = ""; 103 | }; 104 | 232681CB25A7BDFD002E2A1C /* Simple Demo */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 232681CC25A7BDFD002E2A1C /* AppDelegate.swift */, 108 | 232681CE25A7BDFD002E2A1C /* ViewController.swift */, 109 | 232681D025A7BDFE002E2A1C /* Assets.xcassets */, 110 | 232681D225A7BDFE002E2A1C /* Main.storyboard */, 111 | 232681D525A7BDFE002E2A1C /* Info.plist */, 112 | 232681D625A7BDFE002E2A1C /* Simple_Demo.entitlements */, 113 | 23F1282425A834CC0003CABE /* SafariStyleActionBarWindowController.swift */, 114 | 23F1282525A834CC0003CABE /* SafariStyleActionBarWindowController.xib */, 115 | ); 116 | path = "Simple Demo"; 117 | sourceTree = ""; 118 | }; 119 | /* End PBXGroup section */ 120 | 121 | /* Begin PBXNativeTarget section */ 122 | 2325287225A6A4CB00816037 /* DSFActionBar Demo */ = { 123 | isa = PBXNativeTarget; 124 | buildConfigurationList = 2325288325A6A4CC00816037 /* Build configuration list for PBXNativeTarget "DSFActionBar Demo" */; 125 | buildPhases = ( 126 | 2325286F25A6A4CB00816037 /* Sources */, 127 | 2325287025A6A4CB00816037 /* Frameworks */, 128 | 2325287125A6A4CB00816037 /* Resources */, 129 | ); 130 | buildRules = ( 131 | ); 132 | dependencies = ( 133 | ); 134 | name = "DSFActionBar Demo"; 135 | packageProductDependencies = ( 136 | 2325288A25A6A4E900816037 /* DSFActionBar */, 137 | ); 138 | productName = "DSFActionBar Demo"; 139 | productReference = 2325287325A6A4CB00816037 /* DSFActionBar Demo.app */; 140 | productType = "com.apple.product-type.application"; 141 | }; 142 | 232681C925A7BDFD002E2A1C /* Simple Demo */ = { 143 | isa = PBXNativeTarget; 144 | buildConfigurationList = 232681D925A7BDFE002E2A1C /* Build configuration list for PBXNativeTarget "Simple Demo" */; 145 | buildPhases = ( 146 | 232681C625A7BDFD002E2A1C /* Sources */, 147 | 232681C725A7BDFD002E2A1C /* Frameworks */, 148 | 232681C825A7BDFD002E2A1C /* Resources */, 149 | ); 150 | buildRules = ( 151 | ); 152 | dependencies = ( 153 | ); 154 | name = "Simple Demo"; 155 | packageProductDependencies = ( 156 | 232681DC25A7BE20002E2A1C /* DSFActionBar */, 157 | ); 158 | productName = "Simple Demo"; 159 | productReference = 232681CA25A7BDFD002E2A1C /* Simple Demo.app */; 160 | productType = "com.apple.product-type.application"; 161 | }; 162 | /* End PBXNativeTarget section */ 163 | 164 | /* Begin PBXProject section */ 165 | 2325286B25A6A4CB00816037 /* Project object */ = { 166 | isa = PBXProject; 167 | attributes = { 168 | LastSwiftUpdateCheck = 1230; 169 | LastUpgradeCheck = 1230; 170 | TargetAttributes = { 171 | 2325287225A6A4CB00816037 = { 172 | CreatedOnToolsVersion = 12.3; 173 | }; 174 | 232681C925A7BDFD002E2A1C = { 175 | CreatedOnToolsVersion = 12.3; 176 | }; 177 | }; 178 | }; 179 | buildConfigurationList = 2325286E25A6A4CB00816037 /* Build configuration list for PBXProject "DSFActionBar Demo" */; 180 | compatibilityVersion = "Xcode 9.3"; 181 | developmentRegion = en; 182 | hasScannedForEncodings = 0; 183 | knownRegions = ( 184 | en, 185 | Base, 186 | ); 187 | mainGroup = 2325286A25A6A4CB00816037; 188 | productRefGroup = 2325287425A6A4CB00816037 /* Products */; 189 | projectDirPath = ""; 190 | projectRoot = ""; 191 | targets = ( 192 | 2325287225A6A4CB00816037 /* DSFActionBar Demo */, 193 | 232681C925A7BDFD002E2A1C /* Simple Demo */, 194 | ); 195 | }; 196 | /* End PBXProject section */ 197 | 198 | /* Begin PBXResourcesBuildPhase section */ 199 | 2325287125A6A4CB00816037 /* Resources */ = { 200 | isa = PBXResourcesBuildPhase; 201 | buildActionMask = 2147483647; 202 | files = ( 203 | 2325287B25A6A4CC00816037 /* Assets.xcassets in Resources */, 204 | 2325287E25A6A4CC00816037 /* Main.storyboard in Resources */, 205 | ); 206 | runOnlyForDeploymentPostprocessing = 0; 207 | }; 208 | 232681C825A7BDFD002E2A1C /* Resources */ = { 209 | isa = PBXResourcesBuildPhase; 210 | buildActionMask = 2147483647; 211 | files = ( 212 | 232681D125A7BDFE002E2A1C /* Assets.xcassets in Resources */, 213 | 23F1282725A834CC0003CABE /* SafariStyleActionBarWindowController.xib in Resources */, 214 | 232681D425A7BDFE002E2A1C /* Main.storyboard in Resources */, 215 | ); 216 | runOnlyForDeploymentPostprocessing = 0; 217 | }; 218 | /* End PBXResourcesBuildPhase section */ 219 | 220 | /* Begin PBXSourcesBuildPhase section */ 221 | 2325286F25A6A4CB00816037 /* Sources */ = { 222 | isa = PBXSourcesBuildPhase; 223 | buildActionMask = 2147483647; 224 | files = ( 225 | 2325287925A6A4CB00816037 /* ViewController.swift in Sources */, 226 | 2325287725A6A4CB00816037 /* AppDelegate.swift in Sources */, 227 | ); 228 | runOnlyForDeploymentPostprocessing = 0; 229 | }; 230 | 232681C625A7BDFD002E2A1C /* Sources */ = { 231 | isa = PBXSourcesBuildPhase; 232 | buildActionMask = 2147483647; 233 | files = ( 234 | 23F1282625A834CC0003CABE /* SafariStyleActionBarWindowController.swift in Sources */, 235 | 232681CF25A7BDFD002E2A1C /* ViewController.swift in Sources */, 236 | 232681CD25A7BDFD002E2A1C /* AppDelegate.swift in Sources */, 237 | ); 238 | runOnlyForDeploymentPostprocessing = 0; 239 | }; 240 | /* End PBXSourcesBuildPhase section */ 241 | 242 | /* Begin PBXVariantGroup section */ 243 | 2325287C25A6A4CC00816037 /* Main.storyboard */ = { 244 | isa = PBXVariantGroup; 245 | children = ( 246 | 2325287D25A6A4CC00816037 /* Base */, 247 | ); 248 | name = Main.storyboard; 249 | sourceTree = ""; 250 | }; 251 | 232681D225A7BDFE002E2A1C /* Main.storyboard */ = { 252 | isa = PBXVariantGroup; 253 | children = ( 254 | 232681D325A7BDFE002E2A1C /* Base */, 255 | ); 256 | name = Main.storyboard; 257 | sourceTree = ""; 258 | }; 259 | /* End PBXVariantGroup section */ 260 | 261 | /* Begin XCBuildConfiguration section */ 262 | 2325288125A6A4CC00816037 /* Debug */ = { 263 | isa = XCBuildConfiguration; 264 | buildSettings = { 265 | ALWAYS_SEARCH_USER_PATHS = NO; 266 | CLANG_ANALYZER_NONNULL = YES; 267 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 268 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 269 | CLANG_CXX_LIBRARY = "libc++"; 270 | CLANG_ENABLE_MODULES = YES; 271 | CLANG_ENABLE_OBJC_ARC = YES; 272 | CLANG_ENABLE_OBJC_WEAK = YES; 273 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 274 | CLANG_WARN_BOOL_CONVERSION = YES; 275 | CLANG_WARN_COMMA = YES; 276 | CLANG_WARN_CONSTANT_CONVERSION = YES; 277 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 278 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 279 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 280 | CLANG_WARN_EMPTY_BODY = YES; 281 | CLANG_WARN_ENUM_CONVERSION = YES; 282 | CLANG_WARN_INFINITE_RECURSION = YES; 283 | CLANG_WARN_INT_CONVERSION = YES; 284 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 285 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 286 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 287 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 288 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 289 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 290 | CLANG_WARN_STRICT_PROTOTYPES = YES; 291 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 292 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 293 | CLANG_WARN_UNREACHABLE_CODE = YES; 294 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 295 | COPY_PHASE_STRIP = NO; 296 | DEBUG_INFORMATION_FORMAT = dwarf; 297 | ENABLE_STRICT_OBJC_MSGSEND = YES; 298 | ENABLE_TESTABILITY = YES; 299 | GCC_C_LANGUAGE_STANDARD = gnu11; 300 | GCC_DYNAMIC_NO_PIC = NO; 301 | GCC_NO_COMMON_BLOCKS = YES; 302 | GCC_OPTIMIZATION_LEVEL = 0; 303 | GCC_PREPROCESSOR_DEFINITIONS = ( 304 | "DEBUG=1", 305 | "$(inherited)", 306 | ); 307 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 308 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 309 | GCC_WARN_UNDECLARED_SELECTOR = YES; 310 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 311 | GCC_WARN_UNUSED_FUNCTION = YES; 312 | GCC_WARN_UNUSED_VARIABLE = YES; 313 | MACOSX_DEPLOYMENT_TARGET = 10.11; 314 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 315 | MTL_FAST_MATH = YES; 316 | ONLY_ACTIVE_ARCH = YES; 317 | SDKROOT = macosx; 318 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 319 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 320 | }; 321 | name = Debug; 322 | }; 323 | 2325288225A6A4CC00816037 /* Release */ = { 324 | isa = XCBuildConfiguration; 325 | buildSettings = { 326 | ALWAYS_SEARCH_USER_PATHS = NO; 327 | CLANG_ANALYZER_NONNULL = YES; 328 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 329 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 330 | CLANG_CXX_LIBRARY = "libc++"; 331 | CLANG_ENABLE_MODULES = YES; 332 | CLANG_ENABLE_OBJC_ARC = YES; 333 | CLANG_ENABLE_OBJC_WEAK = YES; 334 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 335 | CLANG_WARN_BOOL_CONVERSION = YES; 336 | CLANG_WARN_COMMA = YES; 337 | CLANG_WARN_CONSTANT_CONVERSION = YES; 338 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 339 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 340 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 341 | CLANG_WARN_EMPTY_BODY = YES; 342 | CLANG_WARN_ENUM_CONVERSION = YES; 343 | CLANG_WARN_INFINITE_RECURSION = YES; 344 | CLANG_WARN_INT_CONVERSION = YES; 345 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 346 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 347 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 348 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 349 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 350 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 351 | CLANG_WARN_STRICT_PROTOTYPES = YES; 352 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 353 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 354 | CLANG_WARN_UNREACHABLE_CODE = YES; 355 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 356 | COPY_PHASE_STRIP = NO; 357 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 358 | ENABLE_NS_ASSERTIONS = NO; 359 | ENABLE_STRICT_OBJC_MSGSEND = YES; 360 | GCC_C_LANGUAGE_STANDARD = gnu11; 361 | GCC_NO_COMMON_BLOCKS = YES; 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.11; 369 | MTL_ENABLE_DEBUG_INFO = NO; 370 | MTL_FAST_MATH = YES; 371 | SDKROOT = macosx; 372 | SWIFT_COMPILATION_MODE = wholemodule; 373 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 374 | }; 375 | name = Release; 376 | }; 377 | 2325288425A6A4CC00816037 /* Debug */ = { 378 | isa = XCBuildConfiguration; 379 | buildSettings = { 380 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 381 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 382 | CODE_SIGN_ENTITLEMENTS = "DSFActionBar Demo/DSFActionBar_Demo.entitlements"; 383 | CODE_SIGN_STYLE = Automatic; 384 | COMBINE_HIDPI_IMAGES = YES; 385 | DEVELOPMENT_TEAM = 3L6RK3LGGW; 386 | ENABLE_HARDENED_RUNTIME = YES; 387 | INFOPLIST_FILE = "DSFActionBar Demo/Info.plist"; 388 | LD_RUNPATH_SEARCH_PATHS = ( 389 | "$(inherited)", 390 | "@executable_path/../Frameworks", 391 | ); 392 | MACOSX_DEPLOYMENT_TARGET = 10.11; 393 | PRODUCT_BUNDLE_IDENTIFIER = "com.darrenford.DSFActionBar-Demo"; 394 | PRODUCT_NAME = "$(TARGET_NAME)"; 395 | SWIFT_VERSION = 5.0; 396 | }; 397 | name = Debug; 398 | }; 399 | 2325288525A6A4CC00816037 /* Release */ = { 400 | isa = XCBuildConfiguration; 401 | buildSettings = { 402 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 403 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 404 | CODE_SIGN_ENTITLEMENTS = "DSFActionBar Demo/DSFActionBar_Demo.entitlements"; 405 | CODE_SIGN_STYLE = Automatic; 406 | COMBINE_HIDPI_IMAGES = YES; 407 | DEVELOPMENT_TEAM = 3L6RK3LGGW; 408 | ENABLE_HARDENED_RUNTIME = YES; 409 | INFOPLIST_FILE = "DSFActionBar Demo/Info.plist"; 410 | LD_RUNPATH_SEARCH_PATHS = ( 411 | "$(inherited)", 412 | "@executable_path/../Frameworks", 413 | ); 414 | MACOSX_DEPLOYMENT_TARGET = 10.11; 415 | PRODUCT_BUNDLE_IDENTIFIER = "com.darrenford.DSFActionBar-Demo"; 416 | PRODUCT_NAME = "$(TARGET_NAME)"; 417 | SWIFT_VERSION = 5.0; 418 | }; 419 | name = Release; 420 | }; 421 | 232681D725A7BDFE002E2A1C /* Debug */ = { 422 | isa = XCBuildConfiguration; 423 | buildSettings = { 424 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 425 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 426 | CODE_SIGN_ENTITLEMENTS = "Simple Demo/Simple_Demo.entitlements"; 427 | CODE_SIGN_STYLE = Automatic; 428 | COMBINE_HIDPI_IMAGES = YES; 429 | DEVELOPMENT_TEAM = 3L6RK3LGGW; 430 | ENABLE_HARDENED_RUNTIME = YES; 431 | INFOPLIST_FILE = "Simple Demo/Info.plist"; 432 | LD_RUNPATH_SEARCH_PATHS = ( 433 | "$(inherited)", 434 | "@executable_path/../Frameworks", 435 | ); 436 | MACOSX_DEPLOYMENT_TARGET = 10.11; 437 | PRODUCT_BUNDLE_IDENTIFIER = "com.darrenford.Simple-Demo"; 438 | PRODUCT_NAME = "$(TARGET_NAME)"; 439 | SWIFT_VERSION = 5.0; 440 | }; 441 | name = Debug; 442 | }; 443 | 232681D825A7BDFE002E2A1C /* Release */ = { 444 | isa = XCBuildConfiguration; 445 | buildSettings = { 446 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 447 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 448 | CODE_SIGN_ENTITLEMENTS = "Simple Demo/Simple_Demo.entitlements"; 449 | CODE_SIGN_STYLE = Automatic; 450 | COMBINE_HIDPI_IMAGES = YES; 451 | DEVELOPMENT_TEAM = 3L6RK3LGGW; 452 | ENABLE_HARDENED_RUNTIME = YES; 453 | INFOPLIST_FILE = "Simple Demo/Info.plist"; 454 | LD_RUNPATH_SEARCH_PATHS = ( 455 | "$(inherited)", 456 | "@executable_path/../Frameworks", 457 | ); 458 | MACOSX_DEPLOYMENT_TARGET = 10.11; 459 | PRODUCT_BUNDLE_IDENTIFIER = "com.darrenford.Simple-Demo"; 460 | PRODUCT_NAME = "$(TARGET_NAME)"; 461 | SWIFT_VERSION = 5.0; 462 | }; 463 | name = Release; 464 | }; 465 | /* End XCBuildConfiguration section */ 466 | 467 | /* Begin XCConfigurationList section */ 468 | 2325286E25A6A4CB00816037 /* Build configuration list for PBXProject "DSFActionBar Demo" */ = { 469 | isa = XCConfigurationList; 470 | buildConfigurations = ( 471 | 2325288125A6A4CC00816037 /* Debug */, 472 | 2325288225A6A4CC00816037 /* Release */, 473 | ); 474 | defaultConfigurationIsVisible = 0; 475 | defaultConfigurationName = Release; 476 | }; 477 | 2325288325A6A4CC00816037 /* Build configuration list for PBXNativeTarget "DSFActionBar Demo" */ = { 478 | isa = XCConfigurationList; 479 | buildConfigurations = ( 480 | 2325288425A6A4CC00816037 /* Debug */, 481 | 2325288525A6A4CC00816037 /* Release */, 482 | ); 483 | defaultConfigurationIsVisible = 0; 484 | defaultConfigurationName = Release; 485 | }; 486 | 232681D925A7BDFE002E2A1C /* Build configuration list for PBXNativeTarget "Simple Demo" */ = { 487 | isa = XCConfigurationList; 488 | buildConfigurations = ( 489 | 232681D725A7BDFE002E2A1C /* Debug */, 490 | 232681D825A7BDFE002E2A1C /* Release */, 491 | ); 492 | defaultConfigurationIsVisible = 0; 493 | defaultConfigurationName = Release; 494 | }; 495 | /* End XCConfigurationList section */ 496 | 497 | /* Begin XCSwiftPackageProductDependency section */ 498 | 2325288A25A6A4E900816037 /* DSFActionBar */ = { 499 | isa = XCSwiftPackageProductDependency; 500 | productName = DSFActionBar; 501 | }; 502 | 232681DC25A7BE20002E2A1C /* DSFActionBar */ = { 503 | isa = XCSwiftPackageProductDependency; 504 | productName = DSFActionBar; 505 | }; 506 | /* End XCSwiftPackageProductDependency section */ 507 | }; 508 | rootObject = 2325286B25A6A4CB00816037 /* Project object */; 509 | } 510 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Demo/DSFActionBar Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Demo/DSFActionBar Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Demo/DSFActionBar Demo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // DSFActionBar Demo 4 | // 5 | // Created by Darren Ford on 7/1/21. 6 | // 7 | 8 | import Cocoa 9 | 10 | @main 11 | class AppDelegate: NSObject, NSApplicationDelegate { 12 | 13 | 14 | 15 | 16 | func applicationDidFinishLaunching(_ aNotification: Notification) { 17 | // Insert code here to initialize your application 18 | } 19 | 20 | func applicationWillTerminate(_ aNotification: Notification) { 21 | // Insert code here to tear down your application 22 | } 23 | 24 | 25 | } 26 | 27 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Demo/DSFActionBar Demo/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Demo/DSFActionBar Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Demo/DSFActionBar Demo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Demo/DSFActionBar Demo/DSFActionBar_Demo.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 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Demo/DSFActionBar Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSMainStoryboardFile 26 | Main 27 | NSPrincipalClass 28 | NSApplication 29 | 30 | 31 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Demo/DSFActionBar Demo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // DSFActionBar Demo 4 | // 5 | // Created by Darren Ford on 7/1/21. 6 | // 7 | 8 | import Cocoa 9 | import DSFActionBar 10 | 11 | class ViewController: NSViewController { 12 | @IBOutlet var actionBar1: DSFActionBar! 13 | @IBOutlet var actionBar2: DSFActionBar! 14 | 15 | @IBOutlet weak var actionTabBar1: DSFActionTabBar! 16 | 17 | 18 | let CaterpillarIdentifier = NSUserInterfaceItemIdentifier("caterpillar") 19 | let FishieIdentifier = NSUserInterfaceItemIdentifier("fishie") 20 | 21 | var toggle: Bool = false 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | 26 | // Do any additional setup after loading the view. 27 | 28 | self.actionBar1.actionDelegate = self 29 | 30 | self.actionBar1.add("Jobs", target: self, action: #selector(self.jobItem(_:))) 31 | 32 | self.actionBar1.add("Caterpillar", identifier: CaterpillarIdentifier, target: self, action: #selector(self.caterpillarItem(_:))) 33 | self.actionBar1.item(for: CaterpillarIdentifier)?.disabled = true 34 | 35 | self.actionBar1.add("Toggle Some Items", target: self, action: #selector(self.toggle(_:))) 36 | self.actionBar1.add("Fishie!", identifier: FishieIdentifier, target: self, action: #selector(self.fishieItem(_:))) 37 | 38 | 39 | let m = NSMenu() 40 | m.addItem(withTitle: "first", action: #selector(self.firstItem(_:)), keyEquivalent: "") 41 | m.addItem(withTitle: "second", action: #selector(self.secondItem(_:)), keyEquivalent: "") 42 | m.addItem(withTitle: "third", action: #selector(self.thirdItem(_:)), keyEquivalent: "") 43 | 44 | self.actionBar1.add("Womble", menu: m) 45 | 46 | 47 | self.actionBar2.actionDelegate = self 48 | self.actionBar2.controlSize = .regular 49 | 50 | self.actionBar2.add("first item") { 51 | Swift.print("first item selected ") 52 | } 53 | self.actionBar2.add("second item") { 54 | Swift.print("second item selected ") 55 | } 56 | self.actionBar2.add("third item") { 57 | Swift.print("third item selected ") 58 | } 59 | 60 | self.actionTabBar1.actionTabDelegate = self 61 | 62 | self.actionTabBar1.add("All Items") 63 | self.actionTabBar1.add("Passwords") 64 | self.actionTabBar1.add("Secure Notes") 65 | self.actionTabBar1.add("My Certificates") 66 | self.actionTabBar1.add("Keys") 67 | self.actionTabBar1.add("Certificates") 68 | } 69 | 70 | @objc func toggle(_ sender: AnyObject) { 71 | self.toggle.toggle() 72 | 73 | guard let fishie = self.actionBar1.item(for: FishieIdentifier), 74 | let caterpillar = self.actionBar1.item(for: CaterpillarIdentifier) else { 75 | fatalError() 76 | } 77 | 78 | if self.toggle { 79 | let m = NSMenu() 80 | m.addItem(withTitle: "abc", action: nil, keyEquivalent: "") 81 | m.addItem(withTitle: "def", action: nil, keyEquivalent: "") 82 | m.addItem(withTitle: "ghi", action: nil, keyEquivalent: "") 83 | fishie.menu = m 84 | } 85 | else { 86 | fishie.setAction(#selector(self.fishieItem(_:)), for: self) 87 | } 88 | 89 | caterpillar.disabled = !self.toggle 90 | } 91 | 92 | @objc func firstItem(_: Any) { 93 | Swift.print("first selected!") 94 | } 95 | 96 | @objc func jobItem(_: Any) { 97 | Swift.print("job selected!") 98 | 99 | guard let r = self.actionBar1.item(for: CaterpillarIdentifier)?.position else { 100 | fatalError() 101 | } 102 | 103 | Swift.print("Caterpillar button is located at \(r) within first action bar") 104 | 105 | // Overlay something (like a text field for renaming) 106 | // let b = NSTextField(frame: r!) 107 | // self.actionBar1.addSubview(b) 108 | // self.view.window?.makeFirstResponder(b) 109 | 110 | } 111 | 112 | @objc func caterpillarItem(_: Any) { 113 | Swift.print("caterpillar selected!") 114 | } 115 | 116 | @objc func secondItem(_: Any) { 117 | Swift.print("second selected!") 118 | } 119 | 120 | @objc func thirdItem(_: Any) { 121 | Swift.print("third selected!") 122 | } 123 | 124 | @objc func fishieItem(_: Any) { 125 | Swift.print("fishie selected!") 126 | } 127 | 128 | override var representedObject: Any? { 129 | didSet { 130 | // Update the view, if already loaded. 131 | } 132 | } 133 | } 134 | 135 | extension ViewController: DSFActionBarDelegate { 136 | func actionBar(_ actionBar: DSFActionBar, didReorderItems items: [DSFActionBarItem]) { 137 | guard actionBar === self.actionBar2 else { return } 138 | Swift.print("Did reorder items '\(items)'") 139 | } 140 | 141 | func actionBar(_ actionBar: DSFActionBar, didRightClickOnItem item: DSFActionBarItem) { 142 | guard actionBar === self.actionBar1 else { return } 143 | 144 | Swift.print("Did right-click on item '\(item.title)'") 145 | let m = NSMenu() 146 | let mi1 = m.addItem(withTitle: "Open in tab", action: #selector(a1(_:)), keyEquivalent: "") 147 | mi1.representedObject = item 148 | let mi2 = m.addItem(withTitle: "Open in window", action: #selector(a2(_:)), keyEquivalent: "") 149 | mi2.representedObject = item 150 | 151 | let pos = self.view.window!.mouseLocationOutsideOfEventStream 152 | let ev = actionBar.convert(pos, from: nil) 153 | m.popUp(positioning: nil, at: ev, in: actionBar) 154 | 155 | } 156 | 157 | @objc func a1(_ sender: NSMenuItem) { 158 | let item = sender.representedObject as! DSFActionBarItem 159 | Swift.print(" > Open in tab selected for '\(item.title)'") 160 | } 161 | 162 | @objc func a2(_ sender: NSMenuItem) { 163 | let item = sender.representedObject as! DSFActionBarItem 164 | Swift.print(" > Open in window selected for '\(item.title)'") 165 | } 166 | 167 | } 168 | 169 | extension ViewController: DSFActionTabBarDelegate { 170 | func actionTabBar(_ actionTabBar: DSFActionTabBar, didSelectItem item: DSFActionBarItem, atIndex index: Int) { 171 | Swift.print("Selected tab \(index) for item '\(item.title)'") 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Demo/Simple Demo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Simple Demo 4 | // 5 | // Created by Darren Ford on 8/1/21. 6 | // 7 | 8 | import Cocoa 9 | 10 | @main 11 | class AppDelegate: NSObject, NSApplicationDelegate { 12 | 13 | let safariStyle = SafariStyleActionBarWindowController() 14 | 15 | 16 | func applicationDidFinishLaunching(_ aNotification: Notification) { 17 | // Insert code here to initialize your application 18 | 19 | safariStyle.loadWindow() 20 | safariStyle.showWindow(self) 21 | safariStyle.setup() 22 | 23 | } 24 | 25 | func applicationWillTerminate(_ aNotification: Notification) { 26 | // Insert code here to tear down your application 27 | } 28 | 29 | 30 | } 31 | 32 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Demo/Simple Demo/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Demo/Simple Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Demo/Simple Demo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Demo/Simple Demo/Assets.xcassets/KeychainTabsBackgroundColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.801", 9 | "green" : "0.808", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "display-p3", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.367", 27 | "green" : "0.078", 28 | "red" : "0.197" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Demo/Simple Demo/Assets.xcassets/SafariTabsBackgroundColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.921", 9 | "green" : "0.921", 10 | "red" : "0.921" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "display-p3", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.144", 27 | "green" : "0.110", 28 | "red" : "0.113" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Demo/Simple Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSMainStoryboardFile 26 | Main 27 | NSPrincipalClass 28 | NSApplication 29 | 30 | 31 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Demo/Simple Demo/SafariStyleActionBarWindowController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SafariStyleActionBarWindowController.swift 3 | // Simple Demo 4 | // 5 | // Created by Darren Ford on 8/1/21. 6 | // 7 | 8 | import Cocoa 9 | import DSFActionBar 10 | 11 | class SafariStyleActionBarWindowController: NSWindowController { 12 | override var windowNibName: NSNib.Name? { 13 | return "SafariStyleActionBarWindowController" 14 | } 15 | 16 | @IBOutlet var actionBar: DSFActionBar! 17 | 18 | var fourthItemHasMenu = false 19 | 20 | override func windowDidLoad() { 21 | super.windowDidLoad() 22 | } 23 | 24 | func setup() { 25 | self.actionBar.actionDelegate = self 26 | self.actionBar.canReorder = true 27 | 28 | // Add with a callback block 29 | self.actionBar.add("first item") { 30 | Swift.print("first item selected ") 31 | } 32 | 33 | let m = NSMenu() 34 | m.addItem(withTitle: "first", action: #selector(self.firstItem(_:)), keyEquivalent: "") 35 | m.addItem(withTitle: "second", action: #selector(self.secondItem(_:)), keyEquivalent: "") 36 | m.addItem(withTitle: "third", action: #selector(self.thirdItem(_:)), keyEquivalent: "") 37 | self.actionBar.add("second item", menu: m) 38 | 39 | let thirdItem = self.actionBar.add("third item") { 40 | Swift.print("third item selected ") 41 | } 42 | thirdItem.disabled = true 43 | 44 | self.actionBar.add("fourth item", target: self, action: #selector(self.fourthItem(_:))) 45 | 46 | self.actionBar.add("toggle enabled") { 47 | if let item = self.actionBar.item(title: "third item") { 48 | item.disabled.toggle() 49 | } 50 | } 51 | 52 | self.actionBar.add("toggle menu") { [weak self] in 53 | guard let `self` = self else { return } 54 | if let item = self.actionBar.item(title: "fourth item") { 55 | self.fourthItemHasMenu.toggle() 56 | if self.fourthItemHasMenu { 57 | let m = NSMenu() 58 | m.addItem(withTitle: "five", action: nil, keyEquivalent: "") 59 | m.addItem(withTitle: "six", action: nil, keyEquivalent: "") 60 | item.menu = m 61 | } 62 | else { 63 | item.setAction(#selector(self.fourthItem(_:)), for: self) 64 | } 65 | } 66 | } 67 | 68 | } 69 | 70 | @objc func firstItem(_: Any) { 71 | Swift.print("first menu item selected!") 72 | } 73 | 74 | @objc func secondItem(_: Any) { 75 | Swift.print("second menu item selected!") 76 | } 77 | 78 | @objc func thirdItem(_: Any) { 79 | Swift.print("third menu item selected!") 80 | } 81 | 82 | @objc func fourthItem(_: Any) { 83 | Swift.print("fourth item selected!") 84 | } 85 | } 86 | 87 | extension SafariStyleActionBarWindowController: DSFActionBarDelegate { 88 | 89 | // Demo reorder detection 90 | 91 | func actionBar(_ actionBar: DSFActionBar, didReorderItems items: [DSFActionBarItem]) { 92 | let newOrder = items.map { $0.title } 93 | Swift.print("New item order is: \(newOrder)") 94 | } 95 | 96 | // Demo right-click support 97 | 98 | func actionBar(_ actionBar: DSFActionBar, didRightClickOnItem item: DSFActionBarItem) { 99 | Swift.print("Did right-click on item '\(item.title)'") 100 | let m = NSMenu() 101 | let mi1 = m.addItem(withTitle: "Open in tab", action: #selector(a1(_:)), keyEquivalent: "") 102 | mi1.representedObject = item 103 | let mi2 = m.addItem(withTitle: "Open in window", action: #selector(a2(_:)), keyEquivalent: "") 104 | mi2.representedObject = item 105 | 106 | let pos = self.window!.mouseLocationOutsideOfEventStream 107 | let ev = actionBar.convert(pos, from: nil) 108 | m.popUp(positioning: nil, at: ev, in: actionBar) 109 | } 110 | 111 | @objc func a1(_ sender: NSMenuItem) { 112 | let item = sender.representedObject as! DSFActionBarItem 113 | Swift.print(" > Open in tab selected for '\(item.title)'") 114 | } 115 | 116 | @objc func a2(_ sender: NSMenuItem) { 117 | let item = sender.representedObject as! DSFActionBarItem 118 | Swift.print(" > Open in window selected for '\(item.title)'") 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Demo/Simple Demo/SafariStyleActionBarWindowController.xib: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Demo/Simple Demo/Simple_Demo.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 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Demo/Simple Demo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Simple Demo 4 | // 5 | // Created by Darren Ford on 8/1/21. 6 | // 7 | 8 | import Cocoa 9 | import DSFActionBar 10 | 11 | class ViewController: NSViewController { 12 | 13 | @IBOutlet weak var keychainStyleTabBar: DSFActionTabBar! 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | 17 | // Do any additional setup after loading the view. 18 | 19 | self.keychainStyleTabBar.actionTabDelegate = self 20 | 21 | self.keychainStyleTabBar.edgeInsets = NSEdgeInsets(top: 8, left: 8, bottom: 8, right: 8) 22 | self.keychainStyleTabBar.itemSpacing = 4 23 | 24 | self.keychainStyleTabBar.add("All Items") 25 | self.keychainStyleTabBar.add("Passwords") 26 | self.keychainStyleTabBar.add("Secure Notes") 27 | self.keychainStyleTabBar.add("My Certificates") 28 | self.keychainStyleTabBar.add("Keys") 29 | self.keychainStyleTabBar.add("Certificates") 30 | 31 | } 32 | 33 | override var representedObject: Any? { 34 | didSet { 35 | // Update the view, if already loaded. 36 | } 37 | } 38 | 39 | 40 | } 41 | 42 | extension ViewController: DSFActionTabBarDelegate { 43 | func actionTabBar(_ actionTabBar: DSFActionTabBar, didSelectItem item: DSFActionBarItem, atIndex index: Int) { 44 | Swift.print("Selected tab \(index) for item '\(item.title)'") 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Objc Demo/DSFActionBar Objc Demo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 23F1284325A84CF70003CABE /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 23F1284225A84CF70003CABE /* AppDelegate.m */; }; 11 | 23F1284525A84CF80003CABE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 23F1284425A84CF80003CABE /* Assets.xcassets */; }; 12 | 23F1284825A84CF80003CABE /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 23F1284625A84CF80003CABE /* MainMenu.xib */; }; 13 | 23F1284B25A84CF80003CABE /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 23F1284A25A84CF80003CABE /* main.m */; }; 14 | 23F1285525A84D130003CABE /* DSFActionBar in Frameworks */ = {isa = PBXBuildFile; productRef = 23F1285425A84D130003CABE /* DSFActionBar */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXFileReference section */ 18 | 23F1283E25A84CF70003CABE /* DSFActionBar Objc Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "DSFActionBar Objc Demo.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 19 | 23F1284125A84CF70003CABE /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 20 | 23F1284225A84CF70003CABE /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 21 | 23F1284425A84CF80003CABE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 22 | 23F1284725A84CF80003CABE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 23 | 23F1284925A84CF80003CABE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 24 | 23F1284A25A84CF80003CABE /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 25 | 23F1284C25A84CF80003CABE /* DSFActionBar_Objc_Demo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DSFActionBar_Objc_Demo.entitlements; sourceTree = ""; }; 26 | /* End PBXFileReference section */ 27 | 28 | /* Begin PBXFrameworksBuildPhase section */ 29 | 23F1283B25A84CF70003CABE /* Frameworks */ = { 30 | isa = PBXFrameworksBuildPhase; 31 | buildActionMask = 2147483647; 32 | files = ( 33 | 23F1285525A84D130003CABE /* DSFActionBar in Frameworks */, 34 | ); 35 | runOnlyForDeploymentPostprocessing = 0; 36 | }; 37 | /* End PBXFrameworksBuildPhase section */ 38 | 39 | /* Begin PBXGroup section */ 40 | 23F1283525A84CF70003CABE = { 41 | isa = PBXGroup; 42 | children = ( 43 | 23F1284025A84CF70003CABE /* DSFActionBar Objc Demo */, 44 | 23F1283F25A84CF70003CABE /* Products */, 45 | ); 46 | sourceTree = ""; 47 | }; 48 | 23F1283F25A84CF70003CABE /* Products */ = { 49 | isa = PBXGroup; 50 | children = ( 51 | 23F1283E25A84CF70003CABE /* DSFActionBar Objc Demo.app */, 52 | ); 53 | name = Products; 54 | sourceTree = ""; 55 | }; 56 | 23F1284025A84CF70003CABE /* DSFActionBar Objc Demo */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | 23F1284125A84CF70003CABE /* AppDelegate.h */, 60 | 23F1284225A84CF70003CABE /* AppDelegate.m */, 61 | 23F1284425A84CF80003CABE /* Assets.xcassets */, 62 | 23F1284625A84CF80003CABE /* MainMenu.xib */, 63 | 23F1284925A84CF80003CABE /* Info.plist */, 64 | 23F1284A25A84CF80003CABE /* main.m */, 65 | 23F1284C25A84CF80003CABE /* DSFActionBar_Objc_Demo.entitlements */, 66 | ); 67 | path = "DSFActionBar Objc Demo"; 68 | sourceTree = ""; 69 | }; 70 | /* End PBXGroup section */ 71 | 72 | /* Begin PBXNativeTarget section */ 73 | 23F1283D25A84CF70003CABE /* DSFActionBar Objc Demo */ = { 74 | isa = PBXNativeTarget; 75 | buildConfigurationList = 23F1284F25A84CF80003CABE /* Build configuration list for PBXNativeTarget "DSFActionBar Objc Demo" */; 76 | buildPhases = ( 77 | 23F1283A25A84CF70003CABE /* Sources */, 78 | 23F1283B25A84CF70003CABE /* Frameworks */, 79 | 23F1283C25A84CF70003CABE /* Resources */, 80 | ); 81 | buildRules = ( 82 | ); 83 | dependencies = ( 84 | ); 85 | name = "DSFActionBar Objc Demo"; 86 | packageProductDependencies = ( 87 | 23F1285425A84D130003CABE /* DSFActionBar */, 88 | ); 89 | productName = "DSFActionBar Objc Demo"; 90 | productReference = 23F1283E25A84CF70003CABE /* DSFActionBar Objc Demo.app */; 91 | productType = "com.apple.product-type.application"; 92 | }; 93 | /* End PBXNativeTarget section */ 94 | 95 | /* Begin PBXProject section */ 96 | 23F1283625A84CF70003CABE /* Project object */ = { 97 | isa = PBXProject; 98 | attributes = { 99 | LastUpgradeCheck = 1230; 100 | TargetAttributes = { 101 | 23F1283D25A84CF70003CABE = { 102 | CreatedOnToolsVersion = 12.3; 103 | }; 104 | }; 105 | }; 106 | buildConfigurationList = 23F1283925A84CF70003CABE /* Build configuration list for PBXProject "DSFActionBar Objc Demo" */; 107 | compatibilityVersion = "Xcode 9.3"; 108 | developmentRegion = en; 109 | hasScannedForEncodings = 0; 110 | knownRegions = ( 111 | en, 112 | Base, 113 | ); 114 | mainGroup = 23F1283525A84CF70003CABE; 115 | packageReferences = ( 116 | 23F1285325A84D130003CABE /* XCRemoteSwiftPackageReference "DSFActionBar" */, 117 | ); 118 | productRefGroup = 23F1283F25A84CF70003CABE /* Products */; 119 | projectDirPath = ""; 120 | projectRoot = ""; 121 | targets = ( 122 | 23F1283D25A84CF70003CABE /* DSFActionBar Objc Demo */, 123 | ); 124 | }; 125 | /* End PBXProject section */ 126 | 127 | /* Begin PBXResourcesBuildPhase section */ 128 | 23F1283C25A84CF70003CABE /* Resources */ = { 129 | isa = PBXResourcesBuildPhase; 130 | buildActionMask = 2147483647; 131 | files = ( 132 | 23F1284525A84CF80003CABE /* Assets.xcassets in Resources */, 133 | 23F1284825A84CF80003CABE /* MainMenu.xib in Resources */, 134 | ); 135 | runOnlyForDeploymentPostprocessing = 0; 136 | }; 137 | /* End PBXResourcesBuildPhase section */ 138 | 139 | /* Begin PBXSourcesBuildPhase section */ 140 | 23F1283A25A84CF70003CABE /* Sources */ = { 141 | isa = PBXSourcesBuildPhase; 142 | buildActionMask = 2147483647; 143 | files = ( 144 | 23F1284B25A84CF80003CABE /* main.m in Sources */, 145 | 23F1284325A84CF70003CABE /* AppDelegate.m in Sources */, 146 | ); 147 | runOnlyForDeploymentPostprocessing = 0; 148 | }; 149 | /* End PBXSourcesBuildPhase section */ 150 | 151 | /* Begin PBXVariantGroup section */ 152 | 23F1284625A84CF80003CABE /* MainMenu.xib */ = { 153 | isa = PBXVariantGroup; 154 | children = ( 155 | 23F1284725A84CF80003CABE /* Base */, 156 | ); 157 | name = MainMenu.xib; 158 | sourceTree = ""; 159 | }; 160 | /* End PBXVariantGroup section */ 161 | 162 | /* Begin XCBuildConfiguration section */ 163 | 23F1284D25A84CF80003CABE /* Debug */ = { 164 | isa = XCBuildConfiguration; 165 | buildSettings = { 166 | ALWAYS_SEARCH_USER_PATHS = NO; 167 | CLANG_ANALYZER_NONNULL = YES; 168 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 169 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 170 | CLANG_CXX_LIBRARY = "libc++"; 171 | CLANG_ENABLE_MODULES = YES; 172 | CLANG_ENABLE_OBJC_ARC = YES; 173 | CLANG_ENABLE_OBJC_WEAK = YES; 174 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 175 | CLANG_WARN_BOOL_CONVERSION = YES; 176 | CLANG_WARN_COMMA = YES; 177 | CLANG_WARN_CONSTANT_CONVERSION = YES; 178 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 179 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 180 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 181 | CLANG_WARN_EMPTY_BODY = YES; 182 | CLANG_WARN_ENUM_CONVERSION = YES; 183 | CLANG_WARN_INFINITE_RECURSION = YES; 184 | CLANG_WARN_INT_CONVERSION = YES; 185 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 186 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 187 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 188 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 189 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 190 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 191 | CLANG_WARN_STRICT_PROTOTYPES = YES; 192 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 193 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 194 | CLANG_WARN_UNREACHABLE_CODE = YES; 195 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 196 | COPY_PHASE_STRIP = NO; 197 | DEBUG_INFORMATION_FORMAT = dwarf; 198 | ENABLE_STRICT_OBJC_MSGSEND = YES; 199 | ENABLE_TESTABILITY = YES; 200 | GCC_C_LANGUAGE_STANDARD = gnu11; 201 | GCC_DYNAMIC_NO_PIC = NO; 202 | GCC_NO_COMMON_BLOCKS = YES; 203 | GCC_OPTIMIZATION_LEVEL = 0; 204 | GCC_PREPROCESSOR_DEFINITIONS = ( 205 | "DEBUG=1", 206 | "$(inherited)", 207 | ); 208 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 209 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 210 | GCC_WARN_UNDECLARED_SELECTOR = YES; 211 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 212 | GCC_WARN_UNUSED_FUNCTION = YES; 213 | GCC_WARN_UNUSED_VARIABLE = YES; 214 | MACOSX_DEPLOYMENT_TARGET = 11.1; 215 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 216 | MTL_FAST_MATH = YES; 217 | ONLY_ACTIVE_ARCH = YES; 218 | SDKROOT = macosx; 219 | }; 220 | name = Debug; 221 | }; 222 | 23F1284E25A84CF80003CABE /* Release */ = { 223 | isa = XCBuildConfiguration; 224 | buildSettings = { 225 | ALWAYS_SEARCH_USER_PATHS = NO; 226 | CLANG_ANALYZER_NONNULL = YES; 227 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 228 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 229 | CLANG_CXX_LIBRARY = "libc++"; 230 | CLANG_ENABLE_MODULES = YES; 231 | CLANG_ENABLE_OBJC_ARC = YES; 232 | CLANG_ENABLE_OBJC_WEAK = YES; 233 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 234 | CLANG_WARN_BOOL_CONVERSION = YES; 235 | CLANG_WARN_COMMA = YES; 236 | CLANG_WARN_CONSTANT_CONVERSION = YES; 237 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 238 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 239 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 240 | CLANG_WARN_EMPTY_BODY = YES; 241 | CLANG_WARN_ENUM_CONVERSION = YES; 242 | CLANG_WARN_INFINITE_RECURSION = YES; 243 | CLANG_WARN_INT_CONVERSION = YES; 244 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 245 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 246 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 247 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 248 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 249 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 250 | CLANG_WARN_STRICT_PROTOTYPES = YES; 251 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 252 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 253 | CLANG_WARN_UNREACHABLE_CODE = YES; 254 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 255 | COPY_PHASE_STRIP = NO; 256 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 257 | ENABLE_NS_ASSERTIONS = NO; 258 | ENABLE_STRICT_OBJC_MSGSEND = YES; 259 | GCC_C_LANGUAGE_STANDARD = gnu11; 260 | GCC_NO_COMMON_BLOCKS = YES; 261 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 262 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 263 | GCC_WARN_UNDECLARED_SELECTOR = YES; 264 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 265 | GCC_WARN_UNUSED_FUNCTION = YES; 266 | GCC_WARN_UNUSED_VARIABLE = YES; 267 | MACOSX_DEPLOYMENT_TARGET = 11.1; 268 | MTL_ENABLE_DEBUG_INFO = NO; 269 | MTL_FAST_MATH = YES; 270 | SDKROOT = macosx; 271 | }; 272 | name = Release; 273 | }; 274 | 23F1285025A84CF80003CABE /* Debug */ = { 275 | isa = XCBuildConfiguration; 276 | buildSettings = { 277 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 278 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 279 | CODE_SIGN_ENTITLEMENTS = "DSFActionBar Objc Demo/DSFActionBar_Objc_Demo.entitlements"; 280 | CODE_SIGN_STYLE = Automatic; 281 | COMBINE_HIDPI_IMAGES = YES; 282 | DEVELOPMENT_TEAM = 3L6RK3LGGW; 283 | ENABLE_HARDENED_RUNTIME = YES; 284 | INFOPLIST_FILE = "DSFActionBar Objc Demo/Info.plist"; 285 | LD_RUNPATH_SEARCH_PATHS = ( 286 | "$(inherited)", 287 | "@executable_path/../Frameworks", 288 | ); 289 | PRODUCT_BUNDLE_IDENTIFIER = "com.darrenford.DSFActionBar-Objc-Demo"; 290 | PRODUCT_NAME = "$(TARGET_NAME)"; 291 | }; 292 | name = Debug; 293 | }; 294 | 23F1285125A84CF80003CABE /* Release */ = { 295 | isa = XCBuildConfiguration; 296 | buildSettings = { 297 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 298 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 299 | CODE_SIGN_ENTITLEMENTS = "DSFActionBar Objc Demo/DSFActionBar_Objc_Demo.entitlements"; 300 | CODE_SIGN_STYLE = Automatic; 301 | COMBINE_HIDPI_IMAGES = YES; 302 | DEVELOPMENT_TEAM = 3L6RK3LGGW; 303 | ENABLE_HARDENED_RUNTIME = YES; 304 | INFOPLIST_FILE = "DSFActionBar Objc Demo/Info.plist"; 305 | LD_RUNPATH_SEARCH_PATHS = ( 306 | "$(inherited)", 307 | "@executable_path/../Frameworks", 308 | ); 309 | PRODUCT_BUNDLE_IDENTIFIER = "com.darrenford.DSFActionBar-Objc-Demo"; 310 | PRODUCT_NAME = "$(TARGET_NAME)"; 311 | }; 312 | name = Release; 313 | }; 314 | /* End XCBuildConfiguration section */ 315 | 316 | /* Begin XCConfigurationList section */ 317 | 23F1283925A84CF70003CABE /* Build configuration list for PBXProject "DSFActionBar Objc Demo" */ = { 318 | isa = XCConfigurationList; 319 | buildConfigurations = ( 320 | 23F1284D25A84CF80003CABE /* Debug */, 321 | 23F1284E25A84CF80003CABE /* Release */, 322 | ); 323 | defaultConfigurationIsVisible = 0; 324 | defaultConfigurationName = Release; 325 | }; 326 | 23F1284F25A84CF80003CABE /* Build configuration list for PBXNativeTarget "DSFActionBar Objc Demo" */ = { 327 | isa = XCConfigurationList; 328 | buildConfigurations = ( 329 | 23F1285025A84CF80003CABE /* Debug */, 330 | 23F1285125A84CF80003CABE /* Release */, 331 | ); 332 | defaultConfigurationIsVisible = 0; 333 | defaultConfigurationName = Release; 334 | }; 335 | /* End XCConfigurationList section */ 336 | 337 | /* Begin XCRemoteSwiftPackageReference section */ 338 | 23F1285325A84D130003CABE /* XCRemoteSwiftPackageReference "DSFActionBar" */ = { 339 | isa = XCRemoteSwiftPackageReference; 340 | repositoryURL = "https://github.com/dagronf/DSFActionBar"; 341 | requirement = { 342 | kind = upToNextMajorVersion; 343 | minimumVersion = 1.0.0; 344 | }; 345 | }; 346 | /* End XCRemoteSwiftPackageReference section */ 347 | 348 | /* Begin XCSwiftPackageProductDependency section */ 349 | 23F1285425A84D130003CABE /* DSFActionBar */ = { 350 | isa = XCSwiftPackageProductDependency; 351 | package = 23F1285325A84D130003CABE /* XCRemoteSwiftPackageReference "DSFActionBar" */; 352 | productName = DSFActionBar; 353 | }; 354 | /* End XCSwiftPackageProductDependency section */ 355 | }; 356 | rootObject = 23F1283625A84CF70003CABE /* Project object */; 357 | } 358 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Objc Demo/DSFActionBar Objc Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Objc Demo/DSFActionBar Objc Demo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "DSFActionBar", 6 | "repositoryURL": "https://github.com/dagronf/DSFActionBar", 7 | "state": { 8 | "branch": null, 9 | "revision": "5ae7cc6c91691787ac62ba15886484cbbafe9426", 10 | "version": "1.0.0" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Objc Demo/DSFActionBar Objc Demo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // DSFActionBar Objc Demo 4 | // 5 | // Created by Darren Ford on 8/1/21. 6 | // 7 | 8 | #import 9 | 10 | @import DSFActionBar; 11 | 12 | @interface AppDelegate : NSObject 13 | 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Objc Demo/DSFActionBar Objc Demo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // DSFActionBar Objc Demo 4 | // 5 | // Created by Darren Ford on 8/1/21. 6 | // 7 | 8 | #import "AppDelegate.h" 9 | 10 | 11 | 12 | @interface AppDelegate () 13 | 14 | @property (strong) IBOutlet NSWindow *window; 15 | @property (weak) IBOutlet DSFActionBar *actionBar; 16 | @property (weak) IBOutlet DSFActionTabBar *actionTabBar; 17 | 18 | @end 19 | 20 | @implementation AppDelegate 21 | 22 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { 23 | // Insert code here to initialize your application 24 | 25 | [_actionBar add:@"All Items" identifier:nil block:^{ 26 | NSLog(@"Pressed All Items"); 27 | }]; 28 | [_actionBar add:@"Passwords" identifier:nil block:^{ 29 | NSLog(@"Pressed Passwords"); 30 | }]; 31 | [_actionBar add:@"Secure Notes" identifier:nil block:^{ 32 | NSLog(@"Pressed Secure Notes"); 33 | }]; 34 | [_actionBar add:@"My Certificates" identifier:nil block:^{ 35 | NSLog(@"Pressed My Certificates"); 36 | }]; 37 | [_actionBar add:@"Keys" identifier:nil block:^{ 38 | NSLog(@"Pressed Keys"); 39 | }]; 40 | [_actionBar add:@"Certificates" identifier:nil block:^{ 41 | NSLog(@"Pressed Certificates"); 42 | }]; 43 | 44 | //// 45 | 46 | [_actionTabBar setActionTabDelegate:self]; 47 | [_actionTabBar setCentered:NO]; 48 | [_actionTabBar setItemSpacing:2]; 49 | [_actionTabBar setControlSize:NSControlSizeRegular]; 50 | 51 | [_actionTabBar add:@"All Items" identifier: nil]; 52 | [_actionTabBar add:@"Passwords" identifier: nil]; 53 | [_actionTabBar add:@"Secure Notes" identifier: nil]; 54 | [_actionTabBar add:@"My Certificates" identifier: nil]; 55 | [_actionTabBar add:@"Keys" identifier: nil]; 56 | [_actionTabBar add:@"Certificates" identifier: nil]; 57 | } 58 | 59 | - (void)actionTabBar:(DSFActionTabBar *)actionTabBar didSelectItem:(id)item atIndex:(NSInteger)index { 60 | NSLog(@"Selected tab item %ld (%@)", index, [item title]); 61 | } 62 | 63 | 64 | 65 | - (void)applicationWillTerminate:(NSNotification *)aNotification { 66 | // Insert code here to tear down your application 67 | } 68 | 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Objc Demo/DSFActionBar Objc Demo/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Objc Demo/DSFActionBar Objc Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Objc Demo/DSFActionBar Objc Demo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Objc Demo/DSFActionBar Objc Demo/Base.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 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 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | Default 541 | 542 | 543 | 544 | 545 | 546 | 547 | Left to Right 548 | 549 | 550 | 551 | 552 | 553 | 554 | Right to Left 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | Default 566 | 567 | 568 | 569 | 570 | 571 | 572 | Left to Right 573 | 574 | 575 | 576 | 577 | 578 | 579 | Right to Left 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 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Objc Demo/DSFActionBar Objc Demo/DSFActionBar_Objc_Demo.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 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Objc Demo/DSFActionBar Objc Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSMainNibFile 26 | MainMenu 27 | NSPrincipalClass 28 | NSApplication 29 | 30 | 31 | -------------------------------------------------------------------------------- /Demos/DSFActionBar Objc Demo/DSFActionBar Objc Demo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // DSFActionBar Objc Demo 4 | // 5 | // Created by Darren Ford on 8/1/21. 6 | // 7 | 8 | #import 9 | 10 | int main(int argc, const char * argv[]) { 11 | @autoreleasepool { 12 | // Setup code that might create autoreleased objects goes here. 13 | } 14 | return NSApplicationMain(argc, argv); 15 | } 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Darren Ford 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "DSFActionBar", 8 | platforms: [ 9 | .macOS(.v10_11) 10 | ], 11 | products: [ 12 | // Products define the executables and libraries a package produces, and make them visible to other packages. 13 | .library( 14 | name: "DSFActionBar", 15 | targets: ["DSFActionBar"]), 16 | ], 17 | dependencies: [ 18 | // Dependencies declare other packages that this package depends on. 19 | // .package(url: /* package url */, from: "1.0.0"), 20 | ], 21 | targets: [ 22 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 23 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 24 | .target( 25 | name: "DSFActionBar", 26 | dependencies: []), 27 | .testTarget( 28 | name: "DSFActionBarTests", 29 | dependencies: ["DSFActionBar"]), 30 | ] 31 | ) 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DSFActionBar 2 | 3 | 4 | 5 | 6 | 7 | An editable, draggable bar of buttons and menus similar to Safari's Favorites bar with overflow support for macOS (10.11 and later). 8 | 9 | ![](https://img.shields.io/github/v/tag/dagronf/DSFActionBar) ![](https://img.shields.io/badge/macOS-10.11+-red) ![](https://img.shields.io/badge/Swift-5.3-orange.svg) 10 | ![](https://img.shields.io/badge/License-MIT-lightgrey) [![](https://img.shields.io/badge/spm-compatible-brightgreen.svg?style=flat)](https://swift.org/package-manager) 11 | 12 | # Classes 13 | 14 | ## DSFActionBar 15 | 16 | A collection of buttons (items). If not enough space is available to display all the items, a 'More Items' button appears with the hidden buttons in it. 17 | 18 | This control was inspired by Safari's favorites bar. 19 | 20 | 21 | 22 | 23 | Demo Movie 24 | 25 | ### Features 26 | 27 | * Center or left-align the item collection 28 | * Set the background color 29 | * Add, remove, rename items 30 | * Enable/Disable items 31 | * Right-click on item detection 32 | * Overflow support when the available space is too narrow 33 | * (Optional) dropdown menu support for items 34 | * (Optional) reordering via dragging the items 35 | 36 | ### Example 37 | 38 | ```swift 39 | self.actionBar.actionDelegate = self 40 | 41 | // Add a simple 'job' button with a target/action 42 | self.actionBar.add("Jobs", target: self, action: #selector(self.jobItem(_:))) 43 | 44 | // Add a button with a drop-down menu 45 | let menu = NSMenu() 46 | menu.addItem(withTitle: "first", action: #selector(self.firstItem(_:)), keyEquivalent: "") 47 | menu.addItem(withTitle: "second", action: #selector(self.secondItem(_:)), keyEquivalent: "") 48 | menu.addItem(withTitle: "third", action: #selector(self.thirdItem(_:)), keyEquivalent: "") 49 | self.actionBar.add("Ordering", menu: menu) 50 | ``` 51 | 52 | ## DSFActionTabBar 53 | 54 | A collection of buttons that act as a tab bar. If not enough space is available, a 'More Items' button appears with the 'hidden' tabs in it. 55 | 56 | For an example of this, look at detail pane in KeyChain Access - "All Items", "Password" etc. It is this KeyChain Access app that partially inspired this control - as you will notice that if you resize the window smaller than the tabs it just draws off-screen) 57 | 58 | ### Screenshots 59 | 60 | 61 | 62 | 63 | 64 | Demo Movie 65 | 66 | ### Features 67 | 68 | * Center or left-align the item collection 69 | * Set the background color 70 | * Add, remove, rename items 71 | * Enable/Disable items 72 | * Overflow support when the available space is too narrow 73 | 74 | ### Example 75 | 76 | ```swift 77 | // Set the delegate so that we receive tab selection messages 78 | self.actionTabBar.actionTabDelegate = self 79 | 80 | // Add our items 81 | self.actionTabBar.add("All Items") 82 | self.actionTabBar.add("Passwords") 83 | self.actionTabBar.add("Secure Notes") 84 | self.actionTabBar.add("My Certificates") 85 | self.actionTabBar.add("Keys") 86 | self.actionTabBar.add("Certificates") 87 | 88 | ... 89 | 90 | extension MyViewController: DSFActionTabBarDelegate { 91 | func actionTabBar(_ actionTabBar: DSFActionTabBar, didSelectItem item: DSFActionBarItem, atIndex index: Int) { 92 | Swift.print("Selected tab \(index) for item '\(item.title)'") 93 | } 94 | } 95 | ``` 96 | 97 | # Demo 98 | 99 | You can find some demo apps in the `Demos` subfolder. 100 | 101 | # Installation 102 | 103 | ## Swift Package Manager 104 | 105 | Add `https://github.com/dagronf/DSFActionBar` to your project. 106 | 107 | # Usage 108 | 109 | ## Via Interface Builder 110 | 111 | * Add a custom NSView using Interface Builder, then change the class type to `DSFActionBar` or `DSFActionTabBar` as needed. 112 | 113 | ## Programatically 114 | 115 | ```swift 116 | let actionBar = DSFActionBar(frame: rect) 117 | let tabBar = DSFActionTabBar(frame: rect) 118 | ``` 119 | 120 | # Known issues 121 | 122 | * Dragging after changing the theme (ie. dark mode -> standard) the draw background still uses the color of the previous theme. 123 | 124 | # Thanks 125 | 126 | ## DraggableStackView - Mark Onyschuk 127 | 128 | * Mark Onyschuk on [GitHub](https://github.com/monyschuk) -- [Draggable Stack View](https://gist.github.com/monyschuk/cbca3582b6b996ab54c32e2d7eceaf25) 129 | 130 | # Changes 131 | 132 | ## `1.0.0` 133 | 134 | * Fixed issue when dragging off the bar not resetting the cursor. ([link](https://github.com/dagronf/DSFActionBar/issues/1)) 135 | 136 | 137 | # License 138 | ``` 139 | MIT License 140 | 141 | Copyright (c) 2021 Darren Ford 142 | 143 | Permission is hereby granted, free of charge, to any person obtaining a copy 144 | of this software and associated documentation files (the "Software"), to deal 145 | in the Software without restriction, including without limitation the rights 146 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 147 | copies of the Software, and to permit persons to whom the Software is 148 | furnished to do so, subject to the following conditions: 149 | 150 | The above copyright notice and this permission notice shall be included in all 151 | copies or substantial portions of the Software. 152 | 153 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 154 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 155 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 156 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 157 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 158 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 159 | SOFTWARE. 160 | ``` 161 | -------------------------------------------------------------------------------- /Sources/DSFActionBar/DSFActionBar+protocols.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DSFActionBar+protocols.swift 3 | // DSFActionBar 4 | // 5 | // Created by Darren Ford on 7/1/21. 6 | // 7 | // MIT license 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 10 | // documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 12 | // permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all copies or substantial 15 | // portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 18 | // WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | // OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | // 22 | 23 | #if os(macOS) 24 | 25 | import AppKit 26 | 27 | /// ActionBar delegate callbacks 28 | @objc public protocol DSFActionBarDelegate { 29 | /// Callback when the items within an action bar reordered 30 | @objc optional func actionBar(_ actionBar: DSFActionBar, didReorderItems items: [DSFActionBarItem]) 31 | 32 | /// Callback when an item is right-clicked on 33 | @objc optional func actionBar(_ actionBar: DSFActionBar, didRightClickOnItem item: DSFActionBarItem) 34 | } 35 | 36 | /// ActionTabBar delegate callbacks 37 | @objc public protocol DSFActionTabBarDelegate { 38 | /// Callback when an item in the tab bar is selected. 39 | @objc optional func actionTabBar(_ actionTabBar: DSFActionTabBar, didSelectItem item: DSFActionBarItem, atIndex index: Int) 40 | } 41 | 42 | /// An ActionBar item 43 | @objc public protocol DSFActionBarItem { 44 | /// The item's title 45 | var title: String { get set } 46 | 47 | /// Is the item disabled? 48 | var disabled: Bool { get set } 49 | 50 | /// The current position of the item (in DSFActionBar coordinates) 51 | var position: CGRect { get } 52 | 53 | /// Is the item currently hidden (ie. only available in the menu) 54 | var isHidden: Bool { get } 55 | 56 | /// The item's identifier 57 | var identifier: NSUserInterfaceItemIdentifier? { get } 58 | 59 | /// The item's state 60 | var state: NSControl.StateValue { get set } 61 | 62 | /// The menu to be displayed for the item 63 | var menu: NSMenu? { get set } 64 | 65 | /// The action to perform on 'target' when the item is clicked 66 | func setAction(_ action: Selector, for target: AnyObject) 67 | /// The action associated with the item 68 | var action: Selector? { get } 69 | /// The target for the action associated with the item 70 | var target: AnyObject? { get } 71 | 72 | /// Block handling 73 | var actionBlock: (() -> Void)? { get set } 74 | } 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /Sources/DSFActionBar/DSFActionBar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DSFActionBar.swift 3 | // DSFActionBar 4 | // 5 | // Created by Darren Ford on 7/1/21. 6 | // 7 | // MIT license 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 10 | // documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 12 | // permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all copies or substantial 15 | // portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 18 | // WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | // OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | // 22 | 23 | #if os(macOS) 24 | 25 | import AppKit 26 | 27 | internal let DefaultMoreTooltip = NSLocalizedString("More actions…", comment: "Tooltip for the button that appears when there isn't enough space to display all action buttons") 28 | 29 | @IBDesignable 30 | public class DSFActionBar: NSView { 31 | // MARK: Delegates 32 | 33 | /// If set, the delegate receives callbacks when the items are reordered in the action bar 34 | public weak var actionDelegate: DSFActionBarDelegate? { 35 | didSet { 36 | self.stack.dragDelegate = (self.actionDelegate == nil) ? nil : self 37 | } 38 | } 39 | 40 | // MARK: Public properties 41 | 42 | /// The background color for the control (defaults to clear) 43 | @IBInspectable public var backgroundColor: NSColor = .clear { 44 | didSet { 45 | self.needsDisplay = true 46 | } 47 | } 48 | 49 | @IBInspectable public var itemSpacing: CGFloat = 0 { 50 | didSet { 51 | self.stack.spacing = self.itemSpacing 52 | self.stack.needsLayout = true 53 | } 54 | } 55 | 56 | /// Tooltip to display on the 'more items' button that appears when the items are clipped 57 | @IBInspectable public var moreButtonTooltip: String = DefaultMoreTooltip { 58 | didSet { 59 | self.moreButton.toolTip = self.moreButtonTooltip 60 | } 61 | } 62 | 63 | /// Center the items horizontally within the action bar. Otherwise, left align 64 | @IBInspectable public var centered: Bool = false { 65 | didSet { 66 | self.configurePosition() 67 | } 68 | } 69 | 70 | /// Can the item bar be reordered via dragging? 71 | @IBInspectable public var canReorder: Bool = false { 72 | didSet { 73 | self.stack.canReorder = self.canReorder 74 | } 75 | } 76 | 77 | /// The inset to apply to the items bar 78 | public var edgeInsets = NSEdgeInsets() { 79 | didSet { 80 | self.stack.edgeInsets = self.edgeInsets 81 | } 82 | } 83 | 84 | @IBInspectable public var leftInset: CGFloat = 0 { 85 | didSet { 86 | var ei = self.stack.edgeInsets 87 | ei.left = self.leftInset 88 | self.stack.edgeInsets = ei 89 | } 90 | } 91 | 92 | @IBInspectable public var rightInset: CGFloat = 0 { 93 | didSet { 94 | var ei = self.stack.edgeInsets 95 | ei.right = self.rightInset 96 | self.stack.edgeInsets = ei 97 | } 98 | } 99 | 100 | @IBInspectable public var topInset: CGFloat = 0 { 101 | didSet { 102 | var ei = self.stack.edgeInsets 103 | ei.top = self.topInset 104 | self.stack.edgeInsets = ei 105 | } 106 | } 107 | 108 | @IBInspectable public var bottomInset: CGFloat = 0 { 109 | didSet { 110 | var ei = self.stack.edgeInsets 111 | ei.bottom = self.bottomInset 112 | self.stack.edgeInsets = ei 113 | } 114 | } 115 | 116 | /// The size of the control 117 | @objc public var controlSize: NSControl.ControlSize = .small { 118 | didSet { 119 | self.buttonItems.forEach { 120 | $0.controlSize = self.controlSize 121 | $0.needsLayout = true 122 | } 123 | self.needsLayout = true 124 | } 125 | } 126 | 127 | // MARK: Initialize and cleanup 128 | 129 | @objc override public init(frame frameRect: NSRect) { 130 | super.init(frame: frameRect) 131 | self.setup() 132 | } 133 | 134 | @objc public required init?(coder: NSCoder) { 135 | super.init(coder: coder) 136 | self.setup() 137 | } 138 | 139 | deinit { 140 | self.actionDelegate = nil 141 | self.removeAll() 142 | } 143 | 144 | // MARK: Item Discovery 145 | 146 | /// Return all the items in the order they are presented left-to-right within the action bar 147 | @objc public var items: [DSFActionBarItem] { 148 | return self.buttonItems 149 | } 150 | 151 | /// The number of items in the action bar 152 | @inlinable @objc public var itemCount: Int { 153 | return self.items.count 154 | } 155 | 156 | /// Return an item that matches the provided identifier 157 | @objc public func item(for identifier: NSUserInterfaceItemIdentifier) -> DSFActionBarItem? { 158 | return self.actionButton(for: identifier) 159 | } 160 | 161 | /// Returns the item at the specified index. If the index is out of range, returns nil 162 | @objc public func item(index: Int) -> DSFActionBarItem? { 163 | return self.actionButton(index: index) 164 | } 165 | 166 | /// Returns the item with the specified title. If there are multiple, returns the first one 167 | @objc public func item(title: String) -> DSFActionBarItem? { 168 | return self.buttonItems.first(where: { button in button.title == title }) 169 | } 170 | 171 | // MARK: Add item 172 | 173 | /// Add an item with an (optional) menu 174 | @discardableResult 175 | @objc public func add(_ title: String, 176 | identifier: NSUserInterfaceItemIdentifier? = nil, 177 | menu: NSMenu? = nil) -> DSFActionBarItem { 178 | let button = self.createButton(title, identifier) 179 | button.menu = menu 180 | self.stack.addArrangedSubview(button) 181 | self.updateAfterTabChange() 182 | return button 183 | } 184 | 185 | /// Add a new button item using a target/selector 186 | @discardableResult 187 | @objc public func add(_ title: String, 188 | identifier: NSUserInterfaceItemIdentifier? = nil, 189 | target: AnyObject, 190 | action: Selector) -> DSFActionBarItem { 191 | let button = self.createButton(title, identifier) 192 | button.action = action 193 | button.target = target 194 | self.stack.addArrangedSubview(button) 195 | self.updateAfterTabChange() 196 | return button 197 | } 198 | 199 | /// Add a new button item, using a callback block 200 | @discardableResult 201 | @objc public func add(_ title: String, 202 | identifier: NSUserInterfaceItemIdentifier? = nil, 203 | block: @escaping () -> Void) -> DSFActionBarItem { 204 | let button = self.createButton(title, identifier) 205 | button.actionBlock = block 206 | self.stack.addArrangedSubview(button) 207 | self.updateAfterTabChange() 208 | return button 209 | } 210 | 211 | // MARK: Insert item 212 | 213 | /// Adds an item to the action bar at a specific index. 214 | @objc public func insert(at index: Int, 215 | title: String, 216 | identifier: NSUserInterfaceItemIdentifier? = nil) -> DSFActionBarItem 217 | { 218 | let button = self.createButton(title, identifier) 219 | self.stack.insertArrangedSubview(button, at: index) 220 | self.updateAfterTabChange() 221 | return button 222 | } 223 | 224 | // MARK: Remove item(s) 225 | 226 | /// Remove a item from the action bar 227 | @objc public func remove(item: DSFActionBarItem) -> Bool { 228 | if let itemButton = item as? DSFActionBarButton { 229 | self.stack.removeArrangedSubview(itemButton) 230 | self.updateAfterTabChange() 231 | return true 232 | } 233 | else { 234 | return false 235 | } 236 | } 237 | 238 | /// Remove an item from the action bar using the identifier 239 | @objc public func remove(identifier: NSUserInterfaceItemIdentifier) { 240 | if let button = self.actionButton(for: identifier) { 241 | self.stack.removeArrangedSubview(button) 242 | self.stack.needsLayout = true 243 | self.updateAfterTabChange() 244 | } 245 | } 246 | 247 | /// Remove all the items from the action bar 248 | @objc public func removeAll() { 249 | self.stack.arrangedSubviews.forEach { $0.removeFromSuperview() } 250 | self.stack.needsLayout = true 251 | } 252 | 253 | // MARK: - Private definitions 254 | 255 | lazy var stack: DraggingStackView = { 256 | self.createStack() 257 | }() 258 | 259 | /// The arranged items in the stack as Action Bar Buttons 260 | var buttonItems: [DSFActionBarButton] { 261 | return self.stack.arrangedSubviews.map { $0 as! DSFActionBarButton } 262 | } 263 | 264 | /// The 'More Items' button 265 | lazy var moreButton: NSButton = { 266 | self.createMoreButton() 267 | }() 268 | 269 | // Constraints for positioning 270 | var currentPositioningConstraints: [NSLayoutConstraint] = [] 271 | } 272 | 273 | #endif 274 | -------------------------------------------------------------------------------- /Sources/DSFActionBar/DSFActionTabBar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DSFActionTabBar.swift 3 | // DSFActionBar 4 | // 5 | // Created by Darren Ford on 7/1/21. 6 | // 7 | // MIT license 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 10 | // documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 12 | // permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all copies or substantial 15 | // portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 18 | // WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | // OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | // 22 | 23 | #if os(macOS) 24 | 25 | import AppKit 26 | 27 | @IBDesignable 28 | public class DSFActionTabBar: DSFActionBar { 29 | 30 | @objc public weak var actionTabDelegate: DSFActionTabBarDelegate? 31 | 32 | @objc public override init(frame frameRect: NSRect) { 33 | super.init(frame: frameRect) 34 | self.configure() 35 | } 36 | 37 | @objc public required init?(coder: NSCoder) { 38 | super.init(coder: coder) 39 | self.configure() 40 | } 41 | 42 | internal func configure() { 43 | self.setup() 44 | self.canReorder = false 45 | } 46 | 47 | /// Add an item to the tab bar. 48 | @discardableResult 49 | @objc public func add(_ title: String, identifier: NSUserInterfaceItemIdentifier? = nil) -> DSFActionBarItem { 50 | let item = super.add(title, identifier: identifier) 51 | item.setAction(#selector(clicked(_:)), for: self) 52 | if self.itemCount == 1 { 53 | // First item. Select it! 54 | guard let button = item as? DSFActionBarButton else { 55 | fatalError("Internal error - arranged subviews is empty after add") 56 | } 57 | self.select(button) 58 | } 59 | return item 60 | } 61 | 62 | // MARK: Select tab item 63 | 64 | @objc public func select(_ item: DSFActionBarItem) { 65 | guard let button = item as? DSFActionBarButton else { 66 | fatalError("Unexpected bar item type") 67 | } 68 | self.buttonItems.forEach { $0.state = (button === $0) ? .on : .off } 69 | self.clicked(button) 70 | } 71 | 72 | @objc public func select(index: Int) { 73 | guard let item = self.item(index: index) else { 74 | return 75 | } 76 | self.select(item) 77 | } 78 | 79 | @objc public func select(title: String) { 80 | guard let item = self.item(title: title) else { 81 | return 82 | } 83 | self.select(item) 84 | } 85 | 86 | /// Returns the currently selected tab. If no tab is selected (error?) returns -1 87 | @objc public var selectedTab: Int { 88 | if let item = self.items.enumerated().first (where: { item in 89 | return item.element.state == .on 90 | }) { 91 | return item.offset 92 | } 93 | return -1 94 | } 95 | 96 | /// Bar button callbacks 97 | 98 | @objc internal func clicked(_ sender: DSFActionBarButton) { 99 | self.items.forEach { $0.state = (sender === $0) ? .on : .off } 100 | 101 | /// Notify the delegate on the main queue 102 | DispatchQueue.main.async { [weak self] in 103 | guard let `self` = self else { return } 104 | self.actionTabDelegate?.actionTabBar?(self, didSelectItem: sender, atIndex: sender.tag) 105 | } 106 | 107 | } 108 | 109 | } 110 | 111 | #endif 112 | -------------------------------------------------------------------------------- /Sources/DSFActionBar/private/DSFActionBar+private.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DSFActionBar+private.swift 3 | // DSFActionBar 4 | // 5 | // Created by Darren Ford on 7/1/21. 6 | // 7 | // MIT license 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 10 | // documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 12 | // permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all copies or substantial 15 | // portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 18 | // WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | // OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | // 22 | 23 | #if os(macOS) 24 | 25 | import AppKit 26 | 27 | // Internal protocol for the button to ask or provide information to the action bar 28 | internal protocol DSFActionBarProtocol { 29 | // Retrieve the current position (in Action Bar coordinates) of the specified iten 30 | func rect(for child: DSFActionBarButton) -> CGRect 31 | // Notify the bar that the user right-clicked on a bar item 32 | func rightClick(for child: DSFActionBarButton) 33 | // Returns the current background color set for the bar 34 | var backgroundColor: NSColor { get } 35 | } 36 | 37 | // MARK: Setup 38 | 39 | internal extension DSFActionBar { 40 | func setup() { 41 | self.translatesAutoresizingMaskIntoConstraints = false 42 | 43 | self.stack.canReorder = self.canReorder 44 | 45 | self.addSubview(self.stack) 46 | 47 | // Constraints for the containing stack 48 | self.configurePosition() 49 | 50 | // Constraints for the 'more' button 51 | self.addSubview(self.moreButton) 52 | let constraints2 = [ 53 | NSLayoutConstraint(item: self.moreButton, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1, constant: 0), 54 | NSLayoutConstraint(item: self.moreButton, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 0), 55 | NSLayoutConstraint(item: self.moreButton, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0), 56 | 57 | NSLayoutConstraint(item: self.moreButton, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 16), 58 | ] 59 | self.addConstraints(constraints2) 60 | 61 | self.moreButton.toolTip = self.moreButtonTooltip 62 | } 63 | 64 | func actionButton(for identifier: NSUserInterfaceItemIdentifier) -> DSFActionBarButton? { 65 | let first = self.stack.arrangedSubviews.first(where: { view in view.identifier == identifier }) 66 | if let v = first, let b = v as? DSFActionBarButton { 67 | return b 68 | } 69 | return nil 70 | } 71 | 72 | func actionButton(index: Int) -> DSFActionBarButton? { 73 | guard index >= 0, index < self.stack.arrangedSubviews.count else { 74 | return nil 75 | } 76 | return self.stack.arrangedSubviews[index] as? DSFActionBarButton 77 | } 78 | } 79 | 80 | // MARK: Display and layout 81 | 82 | public extension DSFActionBar { 83 | override func viewDidMoveToWindow() { 84 | super.viewDidMoveToWindow() 85 | self.configurePosition() 86 | } 87 | 88 | override func draw(_: NSRect) { 89 | self.backgroundColor.setFill() 90 | self.bounds.fill() 91 | } 92 | 93 | override func layout() { 94 | super.layout() 95 | 96 | var b = self.bounds 97 | b.size.width -= 12 98 | self.buttonItems.forEach { item in 99 | item.isHidden = b.maxX < item.frame.maxX 100 | } 101 | 102 | self.moreButton.isHidden = self.buttonItems.first(where: { item in 103 | item.isHidden 104 | }) == nil ? true : false 105 | } 106 | } 107 | 108 | // MARK: Item positioning 109 | 110 | extension DSFActionBar { 111 | internal func configurePosition() { 112 | if self.centered { 113 | self.configureCentered() 114 | } 115 | else { 116 | self.configureLeft() 117 | } 118 | } 119 | 120 | private func configureLeft() { 121 | self.removeConstraints(self.currentPositioningConstraints) 122 | 123 | let r = NSLayoutConstraint(item: self.stack, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1, constant: 0) 124 | r.priority = NSLayoutConstraint.Priority(10) 125 | 126 | let constraints = [ 127 | NSLayoutConstraint(item: self.stack, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1, constant: 0), 128 | NSLayoutConstraint(item: self.stack, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 0), 129 | NSLayoutConstraint(item: self.stack, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0), 130 | r, 131 | ] 132 | self.addConstraints(constraints) 133 | self.currentPositioningConstraints = constraints 134 | self.needsLayout = true 135 | } 136 | 137 | private func configureCentered() { 138 | self.removeConstraints(self.currentPositioningConstraints) 139 | 140 | let r = NSLayoutConstraint(item: self.stack, attribute: .left, relatedBy: .greaterThanOrEqual, toItem: self, attribute: .left, multiplier: 1, constant: 0) 141 | 142 | let c = NSLayoutConstraint(item: self.stack, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0) 143 | c.priority = NSLayoutConstraint.Priority(10) 144 | 145 | let constraints = [ 146 | c, 147 | NSLayoutConstraint(item: self.stack, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 0), 148 | NSLayoutConstraint(item: self.stack, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0), 149 | r, 150 | ] 151 | self.addConstraints(constraints) 152 | self.currentPositioningConstraints = constraints 153 | self.needsLayout = true 154 | } 155 | } 156 | 157 | // MARK: - 'More items' button handling 158 | 159 | extension DSFActionBar { 160 | @objc func showButton(_ sender: NSButton) { 161 | let hiddenControls: [NSMenuItem] = self.items.compactMap { control in 162 | 163 | guard control.isHidden else { 164 | return nil 165 | } 166 | 167 | // If the control is disabled, set the action to nil to disable the menu item 168 | let action = (control.disabled == false) ? #selector(menuSelected(_:)) : nil 169 | 170 | let mu = NSMenuItem(title: control.title, 171 | action: action, 172 | keyEquivalent: "") 173 | mu.target = self 174 | mu.isEnabled = (control.disabled == false) 175 | mu.state = control.state 176 | mu.representedObject = control 177 | 178 | if let menu = control.menu { 179 | mu.submenu = menu 180 | } 181 | 182 | return mu 183 | } 184 | 185 | let menu = NSMenu() 186 | menu.items = hiddenControls 187 | 188 | menu.popUp(positioning: nil, at: NSPoint(x: sender.frame.minX, y: sender.frame.minY), in: self) 189 | } 190 | 191 | @objc func menuSelected(_ sender: NSMenuItem) { 192 | guard let control = sender.representedObject as? DSFActionBarButton, 193 | let c = control.cell as? NSButtonCell else { 194 | fatalError("Unexpected control type in action bar") 195 | } 196 | 197 | if c.type.rawValue == NSButton.ButtonType.toggle.rawValue { 198 | control.state = .on 199 | } 200 | 201 | if let block = control.actionBlock { 202 | // If the button has a block action, call it. 203 | block() 204 | } 205 | else { 206 | // Force the action 207 | _ = control.target?.perform(control.action, with: control) 208 | } 209 | } 210 | 211 | } 212 | 213 | // MARK: - Interface builder support 214 | 215 | public extension DSFActionBar { 216 | override func prepareForInterfaceBuilder() { 217 | super.prepareForInterfaceBuilder() 218 | 219 | self.setup() 220 | 221 | self.add("Item 1") 222 | self.add("Item 2") 223 | } 224 | } 225 | 226 | // MARK: - 'More items' button definitions 227 | 228 | internal extension DSFActionBar { 229 | func createMoreButton() -> NSButton { 230 | let moreButton = NSButton() 231 | moreButton.translatesAutoresizingMaskIntoConstraints = false 232 | moreButton.isBordered = false 233 | moreButton.setButtonType(.momentaryChange) 234 | moreButton.image = Self.MoreImage 235 | moreButton.imageScaling = .scaleNone 236 | moreButton.imagePosition = .imageOnly 237 | 238 | moreButton.target = self 239 | moreButton.action = #selector(self.showButton(_:)) 240 | 241 | return moreButton 242 | } 243 | 244 | static var MoreImage: NSImage = { 245 | let im = NSImage(size: NSSize(width: 24, height: 16)) 246 | im.lockFocus() 247 | 248 | NSColor.white.setStroke() 249 | 250 | let path = NSBezierPath() 251 | path.move(to: NSPoint(x: 4, y: 4)) 252 | path.line(to: NSPoint(x: 8, y: 8)) 253 | path.line(to: NSPoint(x: 4, y: 12)) 254 | 255 | path.move(to: NSPoint(x: 8, y: 4)) 256 | path.line(to: NSPoint(x: 12, y: 8)) 257 | path.line(to: NSPoint(x: 8, y: 12)) 258 | 259 | path.lineWidth = 1.5 260 | path.lineCapStyle = .round 261 | path.stroke() 262 | im.unlockFocus() 263 | im.isTemplate = true 264 | return im 265 | }() 266 | } 267 | 268 | // MARK: - Stack building 269 | 270 | internal extension DSFActionBar { 271 | 272 | func updateTags() { 273 | self.buttonItems.enumerated().forEach { $0.element.tag = $0.offset } 274 | } 275 | 276 | func createStack() -> DraggingStackView { 277 | let v = DraggingStackView() 278 | v.translatesAutoresizingMaskIntoConstraints = false 279 | v.orientation = .horizontal 280 | v.alignment = .centerY 281 | v.setHuggingPriority(.defaultHigh, for: .vertical) 282 | v.setContentCompressionResistancePriority(NSLayoutConstraint.Priority(10), for: .horizontal) 283 | v.spacing = 0 284 | v.detachesHiddenViews = false 285 | v.dragDelegate = self 286 | return v 287 | } 288 | 289 | func createButton(_ title: String, _ identifier: NSUserInterfaceItemIdentifier?) -> DSFActionBarButton { 290 | let button = DSFActionBarButton(frame: NSZeroRect) 291 | button.translatesAutoresizingMaskIntoConstraints = false 292 | button.title = title 293 | button.identifier = identifier 294 | button.bezelStyle = .shadowlessSquare 295 | 296 | button.action = nil 297 | button.target = nil 298 | button.actionBlock = nil 299 | button.menu = nil 300 | 301 | button.parent = self 302 | button.controlSize = self.controlSize 303 | 304 | return button 305 | } 306 | 307 | func updateAfterTabChange() { 308 | self.updateTags() 309 | self.needsLayout = true 310 | } 311 | } 312 | 313 | extension DSFActionBar: DSFActionBarProtocol { 314 | func rect(for child: DSFActionBarButton) -> CGRect { 315 | if child.isHidden { 316 | /// If the child is hidden, it won't be visible in the UI. 317 | return .zero 318 | } 319 | let pos = child.convert(child.bounds, to: self) 320 | return pos 321 | } 322 | 323 | func rightClick(for child: DSFActionBarButton) { 324 | self.actionDelegate?.actionBar?(self, didRightClickOnItem: child) 325 | } 326 | } 327 | 328 | // MARK: - DraggingStackView reorder events 329 | 330 | extension DSFActionBar: DraggingStackViewProtocol { 331 | func stackViewDidReorder() { 332 | self.updateTags() 333 | self.actionDelegate?.actionBar?(self, didReorderItems: self.items) 334 | } 335 | } 336 | 337 | #endif 338 | -------------------------------------------------------------------------------- /Sources/DSFActionBar/private/DSFActionBarButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DSFActionBarButton.swift 3 | // DSFActionBar 4 | // 5 | // Created by Darren Ford on 7/1/21. 6 | // 7 | // MIT license 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 10 | // documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 12 | // permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all copies or substantial 15 | // portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 18 | // WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | // OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | // 22 | 23 | #if os(macOS) 24 | 25 | import AppKit 26 | 27 | @IBDesignable 28 | class DSFActionBarButton: NSButton { 29 | // MARK: - Init and setup 30 | 31 | var parent: DSFActionBarProtocol! 32 | 33 | var actionBlock: (() -> Void)? { 34 | didSet { 35 | self.action = nil 36 | self.target = nil 37 | self.menu = nil 38 | } 39 | } 40 | 41 | override init(frame frameRect: NSRect) { 42 | super.init(frame: frameRect) 43 | self.setup() 44 | } 45 | 46 | required init?(coder: NSCoder) { 47 | super.init(coder: coder) 48 | self.setup() 49 | } 50 | 51 | @objc private func setup() { 52 | self.translatesAutoresizingMaskIntoConstraints = false 53 | self.wantsLayer = true 54 | } 55 | 56 | private lazy var buttonLayer: CALayer = { 57 | self.layer! 58 | }() 59 | 60 | // MARK: - Cleanup 61 | 62 | deinit { 63 | if let t = self.trackingArea { 64 | self.removeTrackingArea(t) 65 | } 66 | self.trackingArea = nil 67 | self.menu = nil 68 | self.target = nil 69 | } 70 | 71 | // MARK: - Sizing 72 | 73 | override var intrinsicContentSize: NSSize { 74 | var sz = super.intrinsicContentSize 75 | if #available(macOS 11, *) { 76 | sz.width -= 4 77 | } 78 | return sz 79 | } 80 | 81 | override var controlSize: NSControl.ControlSize { 82 | get { 83 | super.controlSize 84 | } 85 | set { 86 | super.controlSize = newValue 87 | self.updateFont() 88 | } 89 | } 90 | 91 | private func updateFont() { 92 | let fs = NSFont.systemFontSize(for: self.controlSize) 93 | self.font = NSFont.systemFont(ofSize: fs) 94 | self.needsDisplay = true 95 | } 96 | 97 | override func viewDidMoveToWindow() { 98 | super.viewDidMoveToWindow() 99 | 100 | self.updateMenuStatus() 101 | } 102 | 103 | func updateMenuStatus() { 104 | if let _ = self.menu { 105 | self.image = Self.menuImage 106 | self.imageScaling = .scaleNone 107 | self.imagePosition = .imageRight 108 | } 109 | else { 110 | self.image = nil 111 | self.imageScaling = .scaleNone 112 | self.imagePosition = .imageRight 113 | } 114 | } 115 | 116 | override open func drawFocusRingMask() { 117 | let r = NSBezierPath(roundedRect: self.bounds, xRadius: 4, yRadius: 4) 118 | r.fill() 119 | } 120 | 121 | override func updateLayer() { 122 | self.buttonLayer.cornerRadius = 4 123 | } 124 | 125 | override var state: NSControl.StateValue { 126 | get { 127 | return super.state 128 | } 129 | set { 130 | super.state = newValue 131 | if (newValue == .on) { 132 | self.buttonLayer.backgroundColor = self.activeColor.cgColor 133 | } 134 | else { 135 | self.buttonLayer.backgroundColor = nil 136 | } 137 | } 138 | } 139 | 140 | // MARK: - Tracking Area 141 | 142 | private var trackingArea: NSTrackingArea? 143 | override open func updateTrackingAreas() { 144 | super.updateTrackingAreas() 145 | 146 | if let t = self.trackingArea { 147 | self.removeTrackingArea(t) 148 | } 149 | let newTrackingArea = NSTrackingArea( 150 | rect: self.bounds, 151 | options: [ 152 | .mouseEnteredAndExited, 153 | .activeInActiveApp, 154 | ], 155 | owner: self, 156 | userInfo: nil 157 | ) 158 | self.addTrackingArea(newTrackingArea) 159 | } 160 | 161 | // MARK: - Mouse Actions 162 | 163 | private var mouseIsDown: Bool = false 164 | private var mouseInside: Bool = false 165 | private var mouseDragLocationX: CGFloat? 166 | 167 | var hoverColor: NSColor { 168 | return UsingEffectiveAppearance(of: self) { 169 | let hc = parent.backgroundColor.flatContrastColor().withAlphaComponent(0.1) 170 | return hc 171 | } 172 | } 173 | 174 | var pressedColor: NSColor { 175 | return UsingEffectiveAppearance(of: self) { 176 | let hc = parent.backgroundColor.flatContrastColor().withAlphaComponent(0.25) 177 | return hc 178 | } 179 | } 180 | 181 | var activeColor: NSColor { 182 | return UsingEffectiveAppearance(of: self) { 183 | let hc = parent.backgroundColor.flatContrastColor().withAlphaComponent(0.2) 184 | return hc 185 | } 186 | } 187 | 188 | override func mouseEntered(with _: NSEvent) { 189 | guard self.isEnabled else { return } 190 | // Highlight with quaternary label color 191 | 192 | if (self.state == .on) { 193 | self.buttonLayer.backgroundColor = self.activeColor.cgColor 194 | } 195 | else if self.mouseIsDown { 196 | self.buttonLayer.backgroundColor = self.pressedColor.cgColor 197 | } 198 | else { 199 | self.buttonLayer.backgroundColor = self.hoverColor.cgColor 200 | } 201 | self.mouseInside = true 202 | } 203 | 204 | override func mouseExited(with _: NSEvent) { 205 | 206 | if (self.state == .on) { 207 | self.buttonLayer.backgroundColor = self.activeColor.cgColor 208 | } 209 | else { 210 | self.buttonLayer.backgroundColor = nil 211 | } 212 | self.mouseInside = false 213 | } 214 | 215 | override func mouseDown(with _: NSEvent) { 216 | guard self.isEnabled else { return } 217 | self.buttonLayer.backgroundColor = self.pressedColor.cgColor 218 | self.mouseIsDown = true 219 | } 220 | 221 | override func mouseDragged(with event: NSEvent) { 222 | let location = convert(event.locationInWindow, from: nil) 223 | if self.mouseDragLocationX == nil { 224 | self.mouseDragLocationX = location.x 225 | } 226 | else if abs(self.mouseDragLocationX! - location.x) < 10 { 227 | // Do nothing. Need to be sticky to avoid accidental drags 228 | } 229 | else { 230 | // Let the next responder up the chain handle it (should be the action bar!) 231 | self.mouseInside = false 232 | self.mouseDragLocationX = nil 233 | super.mouseDragged(with: event) 234 | } 235 | } 236 | 237 | override func mouseUp(with _: NSEvent) { 238 | if self.mouseInside { 239 | self.buttonLayer.backgroundColor = self.hoverColor.cgColor 240 | if let t = self.target { 241 | _ = t.perform(self.action, with: self) 242 | } 243 | else if let block = self.actionBlock { 244 | block() 245 | } 246 | if let menu = self.menu { 247 | menu.popUp(positioning: nil, at: NSPoint(x: self.bounds.minX, y: self.bounds.maxY + 8), in: self) 248 | } 249 | } 250 | else { 251 | self.buttonLayer.backgroundColor = nil 252 | } 253 | 254 | if (self.state == .on) { 255 | self.buttonLayer.backgroundColor = self.pressedColor.cgColor 256 | } 257 | 258 | self.mouseIsDown = false 259 | } 260 | 261 | override func rightMouseDown(with _: NSEvent) { 262 | self.parent?.rightClick(for: self) 263 | } 264 | } 265 | 266 | extension DSFActionBarButton: DSFActionBarItem { 267 | var position: CGRect { 268 | return self.parent.rect(for: self) 269 | } 270 | 271 | override var menu: NSMenu? { 272 | get { 273 | super.menu 274 | } 275 | set { 276 | super.menu = newValue 277 | if newValue != nil { 278 | self.action = nil 279 | self.target = nil 280 | } 281 | self.updateMenuStatus() 282 | } 283 | } 284 | 285 | var disabled: Bool { 286 | get { 287 | return !super.isEnabled 288 | } 289 | set { 290 | super.isEnabled = !newValue 291 | } 292 | } 293 | 294 | func setAction(_ action: Selector, for target: AnyObject) { 295 | self.action = action 296 | self.target = target 297 | self.menu = nil 298 | self.updateMenuStatus() 299 | } 300 | } 301 | 302 | extension DSFActionBarButton { 303 | static var menuImage: NSImage = { 304 | let im = NSImage(size: NSSize(width: 9, height: 16)) 305 | im.lockFocus() 306 | 307 | NSColor.white.setStroke() 308 | 309 | let path = NSBezierPath() 310 | path.move(to: NSPoint(x: 2, y: 9)) 311 | path.line(to: NSPoint(x: 5, y: 6)) 312 | path.line(to: NSPoint(x: 8, y: 9)) 313 | path.lineWidth = 1.5 314 | path.lineCapStyle = .round 315 | path.stroke() 316 | im.unlockFocus() 317 | im.isTemplate = true 318 | return im 319 | }() 320 | } 321 | 322 | #endif 323 | -------------------------------------------------------------------------------- /Sources/DSFActionBar/private/DraggingStackView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DraggingStackView.swift 3 | // DSFInspectorPanes 4 | // 5 | // Adapted from Mark Onyschuk on [GitHub](https://github.com/monyschuk) -- [Draggable Stack View](https://gist.github.com/monyschuk/cbca3582b6b996ab54c32e2d7eceaf25) 6 | // 7 | // MIT License 8 | // 9 | // Copyright (c) 2021 Darren Ford 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | // 29 | 30 | #if os(macOS) 31 | 32 | import AppKit 33 | 34 | // MARK: - Draggable Stack View 35 | 36 | internal protocol DraggingStackViewProtocol: NSObjectProtocol { 37 | func stackViewDidReorder() 38 | } 39 | 40 | /// Draggable Stack View class 41 | /// 42 | /// Adapted from Mark Onyschuk on [GitHub](https://github.com/monyschuk) -- [Draggable Stack View](https://gist.github.com/monyschuk/cbca3582b6b996ab54c32e2d7eceaf25) 43 | internal class DraggingStackView: NSStackView { 44 | /// Can the stack view be reordered 45 | var canReorder = true 46 | 47 | /// Delegate callback when the stackview finishes reordering 48 | weak var dragDelegate: DraggingStackViewProtocol? 49 | 50 | // MARK: - 51 | 52 | // MARK: Update Function 53 | 54 | var update: (NSStackView, [NSView]) -> Void = { stack, views in 55 | stack.views.forEach { 56 | // stack.removeView($0) 57 | stack.removeArrangedSubview($0) 58 | } 59 | 60 | views.forEach { 61 | // stack.addView($0, in: .leading) 62 | stack.addArrangedSubview($0) 63 | 64 | // switch stack.orientation { 65 | // case .horizontal: 66 | // $0.topAnchor.constraint(equalTo: stack.topAnchor).isActive = true 67 | // $0.bottomAnchor.constraint(equalTo: stack.bottomAnchor).isActive = true 68 | // 69 | // case .vertical: 70 | // $0.leadingAnchor.constraint(equalTo: stack.leadingAnchor).isActive = true 71 | // $0.trailingAnchor.constraint(equalTo: stack.trailingAnchor).isActive = true 72 | // 73 | // default: 74 | // break 75 | // } 76 | } 77 | } 78 | 79 | // MARK: - 80 | 81 | // MARK: Event Handling 82 | 83 | override func mouseDragged(with event: NSEvent) { 84 | if self.canReorder { 85 | let location = convert(event.locationInWindow, from: nil) 86 | if let dragged = views.first(where: { $0.hitTest(location) != nil }) { 87 | self.reorder(view: dragged, event: event) 88 | } 89 | } else { 90 | super.mouseDragged(with: event) 91 | } 92 | } 93 | 94 | private func reorder(view: NSView, event: NSEvent) { 95 | guard let layer = self.layer else { return } 96 | guard let cached = try? self.cacheViews() else { return } 97 | 98 | let container = CALayer() 99 | container.frame = layer.bounds 100 | container.zPosition = 1 101 | container.backgroundColor = NSColor.underPageBackgroundColor.cgColor 102 | 103 | cached 104 | .filter { $0.view !== view } 105 | .forEach { container.addSublayer($0) } 106 | 107 | layer.addSublayer(container) 108 | defer { container.removeFromSuperlayer() } 109 | 110 | let dragged = cached.first(where: { $0.view === view })! 111 | 112 | dragged.zPosition = 2 113 | layer.addSublayer(dragged) 114 | defer { dragged.removeFromSuperlayer() } 115 | 116 | let d0 = view.frame.origin 117 | let p0 = convert(event.locationInWindow, from: nil) 118 | 119 | NSCursor.closedHand.set() 120 | 121 | window!.trackEvents(matching: [.leftMouseDragged, .leftMouseUp], timeout: 1e6, mode: RunLoop.Mode.eventTracking) { event, stop in 122 | guard let event = event else { 123 | return 124 | } 125 | if event.type == .leftMouseDragged { 126 | let p1 = self.convert(event.locationInWindow, from: nil) 127 | 128 | let dx = (self.orientation == .horizontal) ? p1.x - p0.x : 0 129 | let dy = (self.orientation == .vertical) ? p1.y - p0.y : 0 130 | 131 | CATransaction.begin() 132 | CATransaction.setDisableActions(true) 133 | dragged.frame.origin.x = d0.x + dx 134 | dragged.frame.origin.y = d0.y + dy 135 | CATransaction.commit() 136 | 137 | let reordered = self.views.map { 138 | (view: $0, 139 | position: $0 !== view 140 | ? NSPoint(x: $0.frame.midX, y: $0.frame.midY) 141 | : NSPoint(x: dragged.frame.midX, y: dragged.frame.midY)) 142 | } 143 | .sorted { 144 | switch self.orientation { 145 | case .vertical: return $0.position.y < $1.position.y 146 | case .horizontal: return $0.position.x < $1.position.x 147 | default: return true 148 | } 149 | } 150 | .map { $0.view } 151 | 152 | let nextIndex = reordered.firstIndex(of: view)! 153 | let prevIndex = self.views.firstIndex(of: view)! 154 | 155 | if nextIndex != prevIndex { 156 | self.update(self, reordered) 157 | self.layoutSubtreeIfNeeded() 158 | 159 | CATransaction.begin() 160 | CATransaction.setAnimationDuration(0.15) 161 | CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)) 162 | 163 | for layer in cached { 164 | layer.position = NSPoint(x: layer.view.frame.midX, y: layer.view.frame.midY) 165 | } 166 | 167 | CATransaction.setCompletionBlock { 168 | // Bug: https://github.com/dagronf/DSFActionBar/issues/1 169 | // During the animation, the new view under the cursor can trigger a cursor reset. As we are still 170 | // dragging, then force the cursor back to what we want. 171 | NSCursor.closedHand.set() 172 | } 173 | 174 | CATransaction.commit() 175 | } 176 | } else { 177 | view.mouseUp(with: event) 178 | NSCursor.arrow.set() 179 | stop.pointee = true 180 | self.dragDelegate?.stackViewDidReorder() 181 | } 182 | } 183 | } 184 | 185 | // MARK: - 186 | 187 | // MARK: View Caching 188 | 189 | private class CachedViewLayer: CALayer { 190 | let view: NSView! 191 | 192 | enum CacheError: Error { 193 | case bitmapCreationFailed 194 | } 195 | 196 | override init(layer: Any) { 197 | self.view = (layer as! CachedViewLayer).view 198 | super.init(layer: layer) 199 | } 200 | 201 | init(view: NSView) throws { 202 | self.view = view 203 | 204 | super.init() 205 | 206 | guard let bitmap = view.bitmapImageRepForCachingDisplay(in: view.bounds) else { throw CacheError.bitmapCreationFailed } 207 | view.cacheDisplay(in: view.bounds, to: bitmap) 208 | 209 | frame = view.frame 210 | contents = bitmap.cgImage 211 | } 212 | 213 | required init?(coder _: NSCoder) { 214 | fatalError("init(coder:) has not been implemented") 215 | } 216 | } 217 | 218 | private func cacheViews() throws -> [CachedViewLayer] { 219 | return try views.map { try cacheView(view: $0) } 220 | } 221 | 222 | private func cacheView(view: NSView) throws -> CachedViewLayer { 223 | return try CachedViewLayer(view: view) 224 | } 225 | } 226 | 227 | #endif 228 | -------------------------------------------------------------------------------- /Sources/DSFActionBar/private/Utilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utilities.swift 3 | // DSFActionBar 4 | // 5 | // Created by Darren Ford on 7/1/21. 6 | // 7 | // MIT license 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 10 | // documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 12 | // permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all copies or substantial 15 | // portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 18 | // WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | // OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | // 22 | 23 | #if os(macOS) 24 | 25 | import AppKit 26 | 27 | /// Perform the supplied block using the appearance settings of the specified view 28 | @inlinable func UsingEffectiveAppearance(of view: NSView, perform block: () throws -> T) rethrows -> T { 29 | let saved = NSAppearance.current 30 | NSAppearance.current = view.effectiveAppearance 31 | let result = try block() 32 | NSAppearance.current = saved 33 | return result 34 | } 35 | 36 | extension NSColor { 37 | /// Returns a black or white contrasting color for this color 38 | /// - Parameter defaultColor: If the color cannot be converted to the genericRGB colorspace, or the input color is .clear, the fallback color 39 | /// - Returns: black or white depending on which provides the greatest contrast to this color 40 | func flatContrastColor(defaultColor: NSColor = .textColor) -> NSColor { 41 | if let rgbColor = self.usingColorSpace(.genericRGB), 42 | rgbColor != NSColor.clear { 43 | let r = 0.299 * rgbColor.redComponent 44 | let g = 0.587 * rgbColor.greenComponent 45 | let b = 0.114 * rgbColor.blueComponent 46 | let avgGray: CGFloat = 1 - (r + g + b) 47 | return (avgGray >= 0.45) ? .white : .black 48 | } 49 | return defaultColor 50 | } 51 | } 52 | 53 | /// Convenience function for optionally updating a value 54 | /// - Parameters: 55 | /// - result: The property to optionally update if 'val' is not equal to its value 56 | /// - val: The value to check against 57 | @inlinable internal func UpdateIfNotEqual(_ result: inout T, _ val: T) where T: Equatable { 58 | if result != val { 59 | result = val 60 | } 61 | } 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /Tests/DSFActionBarTests/DSFActionBarTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import DSFActionBar 3 | 4 | final class DSFActionBarTests: XCTestCase { 5 | func testExample() { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | } 10 | 11 | static var allTests = [ 12 | ("testExample", testExample), 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /Tests/DSFActionBarTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(DSFButtonBarTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import DSFButtonBarTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += DSFButtonBarTests.allTests() 7 | XCTMain(tests) 8 | --------------------------------------------------------------------------------