├── LICENSE ├── MBDoubleSliderExample.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── Viorel.xcuserdatad │ └── xcschemes │ ├── MBDoubleSliderExample.xcscheme │ └── xcschememanagement.plist ├── MBDoubleSliderExample ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ └── Main.storyboard ├── Documents.png ├── Info.plist ├── MBCustomKnob.swift ├── MBDoubleSlider.swift ├── MBFilledView.swift ├── MBTransparentWindow.swift ├── ViewController.swift ├── example.png └── ibInspectable.png └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Cornel Viorel 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 | -------------------------------------------------------------------------------- /MBDoubleSliderExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | F28C7A2D1E314C71009059C5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F28C7A2C1E314C71009059C5 /* AppDelegate.swift */; }; 11 | F28C7A2F1E314C71009059C5 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F28C7A2E1E314C71009059C5 /* ViewController.swift */; }; 12 | F28C7A311E314C71009059C5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F28C7A301E314C71009059C5 /* Assets.xcassets */; }; 13 | F28C7A341E314C71009059C5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F28C7A321E314C71009059C5 /* Main.storyboard */; }; 14 | F28C7A3D1E314C7A009059C5 /* MBCustomKnob.swift in Sources */ = {isa = PBXBuildFile; fileRef = F28C7A3B1E314C7A009059C5 /* MBCustomKnob.swift */; }; 15 | F28C7A3E1E314C7A009059C5 /* MBDoubleSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F28C7A3C1E314C7A009059C5 /* MBDoubleSlider.swift */; }; 16 | F28C7A411E314F01009059C5 /* MBTransparentWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F28C7A401E314F01009059C5 /* MBTransparentWindow.swift */; }; 17 | F28C7A441E314F6F009059C5 /* MBFilledView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F28C7A431E314F6F009059C5 /* MBFilledView.swift */; }; 18 | F28C7A461E3157F3009059C5 /* example.png in Resources */ = {isa = PBXBuildFile; fileRef = F28C7A451E3157F3009059C5 /* example.png */; }; 19 | F28C7A481E315924009059C5 /* ibInspectable.png in Resources */ = {isa = PBXBuildFile; fileRef = F28C7A471E315924009059C5 /* ibInspectable.png */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXFileReference section */ 23 | F28C7A291E314C71009059C5 /* MBDoubleSliderExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MBDoubleSliderExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | F28C7A2C1E314C71009059C5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 25 | F28C7A2E1E314C71009059C5 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 26 | F28C7A301E314C71009059C5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 27 | F28C7A331E314C71009059C5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 28 | F28C7A351E314C71009059C5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 29 | F28C7A3B1E314C7A009059C5 /* MBCustomKnob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MBCustomKnob.swift; sourceTree = ""; }; 30 | F28C7A3C1E314C7A009059C5 /* MBDoubleSlider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MBDoubleSlider.swift; sourceTree = ""; }; 31 | F28C7A401E314F01009059C5 /* MBTransparentWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MBTransparentWindow.swift; sourceTree = ""; }; 32 | F28C7A431E314F6F009059C5 /* MBFilledView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MBFilledView.swift; sourceTree = ""; }; 33 | F28C7A451E3157F3009059C5 /* example.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = example.png; sourceTree = ""; }; 34 | F28C7A471E315924009059C5 /* ibInspectable.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ibInspectable.png; sourceTree = ""; }; 35 | /* End PBXFileReference section */ 36 | 37 | /* Begin PBXFrameworksBuildPhase section */ 38 | F28C7A261E314C71009059C5 /* Frameworks */ = { 39 | isa = PBXFrameworksBuildPhase; 40 | buildActionMask = 2147483647; 41 | files = ( 42 | ); 43 | runOnlyForDeploymentPostprocessing = 0; 44 | }; 45 | /* End PBXFrameworksBuildPhase section */ 46 | 47 | /* Begin PBXGroup section */ 48 | F28C7A201E314C71009059C5 = { 49 | isa = PBXGroup; 50 | children = ( 51 | F28C7A2B1E314C71009059C5 /* MBDoubleSliderExample */, 52 | F28C7A2A1E314C71009059C5 /* Products */, 53 | ); 54 | sourceTree = ""; 55 | }; 56 | F28C7A2A1E314C71009059C5 /* Products */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | F28C7A291E314C71009059C5 /* MBDoubleSliderExample.app */, 60 | ); 61 | name = Products; 62 | sourceTree = ""; 63 | }; 64 | F28C7A2B1E314C71009059C5 /* MBDoubleSliderExample */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | F28C7A2C1E314C71009059C5 /* AppDelegate.swift */, 68 | F28C7A2E1E314C71009059C5 /* ViewController.swift */, 69 | F28C7A421E314F17009059C5 /* OtherResouces */, 70 | F28C7A3F1E314C7F009059C5 /* MBDoubleSlider */, 71 | F28C7A301E314C71009059C5 /* Assets.xcassets */, 72 | F28C7A321E314C71009059C5 /* Main.storyboard */, 73 | F28C7A351E314C71009059C5 /* Info.plist */, 74 | ); 75 | path = MBDoubleSliderExample; 76 | sourceTree = ""; 77 | }; 78 | F28C7A3F1E314C7F009059C5 /* MBDoubleSlider */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | F28C7A3B1E314C7A009059C5 /* MBCustomKnob.swift */, 82 | F28C7A3C1E314C7A009059C5 /* MBDoubleSlider.swift */, 83 | ); 84 | name = MBDoubleSlider; 85 | sourceTree = ""; 86 | }; 87 | F28C7A421E314F17009059C5 /* OtherResouces */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | F28C7A451E3157F3009059C5 /* example.png */, 91 | F28C7A471E315924009059C5 /* ibInspectable.png */, 92 | F28C7A401E314F01009059C5 /* MBTransparentWindow.swift */, 93 | F28C7A431E314F6F009059C5 /* MBFilledView.swift */, 94 | ); 95 | name = OtherResouces; 96 | sourceTree = ""; 97 | }; 98 | /* End PBXGroup section */ 99 | 100 | /* Begin PBXNativeTarget section */ 101 | F28C7A281E314C71009059C5 /* MBDoubleSliderExample */ = { 102 | isa = PBXNativeTarget; 103 | buildConfigurationList = F28C7A381E314C71009059C5 /* Build configuration list for PBXNativeTarget "MBDoubleSliderExample" */; 104 | buildPhases = ( 105 | F28C7A251E314C71009059C5 /* Sources */, 106 | F28C7A261E314C71009059C5 /* Frameworks */, 107 | F28C7A271E314C71009059C5 /* Resources */, 108 | ); 109 | buildRules = ( 110 | ); 111 | dependencies = ( 112 | ); 113 | name = MBDoubleSliderExample; 114 | productName = MBDoubleSliderExample; 115 | productReference = F28C7A291E314C71009059C5 /* MBDoubleSliderExample.app */; 116 | productType = "com.apple.product-type.application"; 117 | }; 118 | /* End PBXNativeTarget section */ 119 | 120 | /* Begin PBXProject section */ 121 | F28C7A211E314C71009059C5 /* Project object */ = { 122 | isa = PBXProject; 123 | attributes = { 124 | LastSwiftUpdateCheck = 0820; 125 | LastUpgradeCheck = 0820; 126 | ORGANIZATIONNAME = Viorel; 127 | TargetAttributes = { 128 | F28C7A281E314C71009059C5 = { 129 | CreatedOnToolsVersion = 8.2.1; 130 | DevelopmentTeam = X34UB4R785; 131 | ProvisioningStyle = Automatic; 132 | }; 133 | }; 134 | }; 135 | buildConfigurationList = F28C7A241E314C71009059C5 /* Build configuration list for PBXProject "MBDoubleSliderExample" */; 136 | compatibilityVersion = "Xcode 3.2"; 137 | developmentRegion = English; 138 | hasScannedForEncodings = 0; 139 | knownRegions = ( 140 | en, 141 | Base, 142 | ); 143 | mainGroup = F28C7A201E314C71009059C5; 144 | productRefGroup = F28C7A2A1E314C71009059C5 /* Products */; 145 | projectDirPath = ""; 146 | projectRoot = ""; 147 | targets = ( 148 | F28C7A281E314C71009059C5 /* MBDoubleSliderExample */, 149 | ); 150 | }; 151 | /* End PBXProject section */ 152 | 153 | /* Begin PBXResourcesBuildPhase section */ 154 | F28C7A271E314C71009059C5 /* Resources */ = { 155 | isa = PBXResourcesBuildPhase; 156 | buildActionMask = 2147483647; 157 | files = ( 158 | F28C7A461E3157F3009059C5 /* example.png in Resources */, 159 | F28C7A481E315924009059C5 /* ibInspectable.png in Resources */, 160 | F28C7A311E314C71009059C5 /* Assets.xcassets in Resources */, 161 | F28C7A341E314C71009059C5 /* Main.storyboard in Resources */, 162 | ); 163 | runOnlyForDeploymentPostprocessing = 0; 164 | }; 165 | /* End PBXResourcesBuildPhase section */ 166 | 167 | /* Begin PBXSourcesBuildPhase section */ 168 | F28C7A251E314C71009059C5 /* Sources */ = { 169 | isa = PBXSourcesBuildPhase; 170 | buildActionMask = 2147483647; 171 | files = ( 172 | F28C7A3E1E314C7A009059C5 /* MBDoubleSlider.swift in Sources */, 173 | F28C7A441E314F6F009059C5 /* MBFilledView.swift in Sources */, 174 | F28C7A2F1E314C71009059C5 /* ViewController.swift in Sources */, 175 | F28C7A2D1E314C71009059C5 /* AppDelegate.swift in Sources */, 176 | F28C7A411E314F01009059C5 /* MBTransparentWindow.swift in Sources */, 177 | F28C7A3D1E314C7A009059C5 /* MBCustomKnob.swift in Sources */, 178 | ); 179 | runOnlyForDeploymentPostprocessing = 0; 180 | }; 181 | /* End PBXSourcesBuildPhase section */ 182 | 183 | /* Begin PBXVariantGroup section */ 184 | F28C7A321E314C71009059C5 /* Main.storyboard */ = { 185 | isa = PBXVariantGroup; 186 | children = ( 187 | F28C7A331E314C71009059C5 /* Base */, 188 | ); 189 | name = Main.storyboard; 190 | sourceTree = ""; 191 | }; 192 | /* End PBXVariantGroup section */ 193 | 194 | /* Begin XCBuildConfiguration section */ 195 | F28C7A361E314C71009059C5 /* Debug */ = { 196 | isa = XCBuildConfiguration; 197 | buildSettings = { 198 | ALWAYS_SEARCH_USER_PATHS = NO; 199 | CLANG_ANALYZER_NONNULL = YES; 200 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 201 | CLANG_CXX_LIBRARY = "libc++"; 202 | CLANG_ENABLE_MODULES = YES; 203 | CLANG_ENABLE_OBJC_ARC = YES; 204 | CLANG_WARN_BOOL_CONVERSION = YES; 205 | CLANG_WARN_CONSTANT_CONVERSION = YES; 206 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 207 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 208 | CLANG_WARN_EMPTY_BODY = YES; 209 | CLANG_WARN_ENUM_CONVERSION = YES; 210 | CLANG_WARN_INFINITE_RECURSION = YES; 211 | CLANG_WARN_INT_CONVERSION = YES; 212 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 213 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 214 | CLANG_WARN_UNREACHABLE_CODE = YES; 215 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 216 | CODE_SIGN_IDENTITY = "-"; 217 | COPY_PHASE_STRIP = NO; 218 | DEBUG_INFORMATION_FORMAT = dwarf; 219 | ENABLE_STRICT_OBJC_MSGSEND = YES; 220 | ENABLE_TESTABILITY = YES; 221 | GCC_C_LANGUAGE_STANDARD = gnu99; 222 | GCC_DYNAMIC_NO_PIC = NO; 223 | GCC_NO_COMMON_BLOCKS = YES; 224 | GCC_OPTIMIZATION_LEVEL = 0; 225 | GCC_PREPROCESSOR_DEFINITIONS = ( 226 | "DEBUG=1", 227 | "$(inherited)", 228 | ); 229 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 230 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 231 | GCC_WARN_UNDECLARED_SELECTOR = YES; 232 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 233 | GCC_WARN_UNUSED_FUNCTION = YES; 234 | GCC_WARN_UNUSED_VARIABLE = YES; 235 | MACOSX_DEPLOYMENT_TARGET = 10.12; 236 | MTL_ENABLE_DEBUG_INFO = YES; 237 | ONLY_ACTIVE_ARCH = YES; 238 | SDKROOT = macosx; 239 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 240 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 241 | }; 242 | name = Debug; 243 | }; 244 | F28C7A371E314C71009059C5 /* Release */ = { 245 | isa = XCBuildConfiguration; 246 | buildSettings = { 247 | ALWAYS_SEARCH_USER_PATHS = NO; 248 | CLANG_ANALYZER_NONNULL = YES; 249 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 250 | CLANG_CXX_LIBRARY = "libc++"; 251 | CLANG_ENABLE_MODULES = YES; 252 | CLANG_ENABLE_OBJC_ARC = YES; 253 | CLANG_WARN_BOOL_CONVERSION = YES; 254 | CLANG_WARN_CONSTANT_CONVERSION = YES; 255 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 256 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 257 | CLANG_WARN_EMPTY_BODY = YES; 258 | CLANG_WARN_ENUM_CONVERSION = YES; 259 | CLANG_WARN_INFINITE_RECURSION = YES; 260 | CLANG_WARN_INT_CONVERSION = YES; 261 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 262 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 263 | CLANG_WARN_UNREACHABLE_CODE = YES; 264 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 265 | CODE_SIGN_IDENTITY = "-"; 266 | COPY_PHASE_STRIP = NO; 267 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 268 | ENABLE_NS_ASSERTIONS = NO; 269 | ENABLE_STRICT_OBJC_MSGSEND = YES; 270 | GCC_C_LANGUAGE_STANDARD = gnu99; 271 | GCC_NO_COMMON_BLOCKS = YES; 272 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 273 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 274 | GCC_WARN_UNDECLARED_SELECTOR = YES; 275 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 276 | GCC_WARN_UNUSED_FUNCTION = YES; 277 | GCC_WARN_UNUSED_VARIABLE = YES; 278 | MACOSX_DEPLOYMENT_TARGET = 10.12; 279 | MTL_ENABLE_DEBUG_INFO = NO; 280 | SDKROOT = macosx; 281 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 282 | }; 283 | name = Release; 284 | }; 285 | F28C7A391E314C71009059C5 /* Debug */ = { 286 | isa = XCBuildConfiguration; 287 | buildSettings = { 288 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 289 | COMBINE_HIDPI_IMAGES = YES; 290 | DEVELOPMENT_TEAM = X34UB4R785; 291 | INFOPLIST_FILE = MBDoubleSliderExample/Info.plist; 292 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 293 | PRODUCT_BUNDLE_IDENTIFIER = com.viorel.MBDoubleSliderExample; 294 | PRODUCT_NAME = "$(TARGET_NAME)"; 295 | SWIFT_VERSION = 3.0; 296 | }; 297 | name = Debug; 298 | }; 299 | F28C7A3A1E314C71009059C5 /* Release */ = { 300 | isa = XCBuildConfiguration; 301 | buildSettings = { 302 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 303 | COMBINE_HIDPI_IMAGES = YES; 304 | DEVELOPMENT_TEAM = X34UB4R785; 305 | INFOPLIST_FILE = MBDoubleSliderExample/Info.plist; 306 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 307 | PRODUCT_BUNDLE_IDENTIFIER = com.viorel.MBDoubleSliderExample; 308 | PRODUCT_NAME = "$(TARGET_NAME)"; 309 | SWIFT_VERSION = 3.0; 310 | }; 311 | name = Release; 312 | }; 313 | /* End XCBuildConfiguration section */ 314 | 315 | /* Begin XCConfigurationList section */ 316 | F28C7A241E314C71009059C5 /* Build configuration list for PBXProject "MBDoubleSliderExample" */ = { 317 | isa = XCConfigurationList; 318 | buildConfigurations = ( 319 | F28C7A361E314C71009059C5 /* Debug */, 320 | F28C7A371E314C71009059C5 /* Release */, 321 | ); 322 | defaultConfigurationIsVisible = 0; 323 | defaultConfigurationName = Release; 324 | }; 325 | F28C7A381E314C71009059C5 /* Build configuration list for PBXNativeTarget "MBDoubleSliderExample" */ = { 326 | isa = XCConfigurationList; 327 | buildConfigurations = ( 328 | F28C7A391E314C71009059C5 /* Debug */, 329 | F28C7A3A1E314C71009059C5 /* Release */, 330 | ); 331 | defaultConfigurationIsVisible = 0; 332 | defaultConfigurationName = Release; 333 | }; 334 | /* End XCConfigurationList section */ 335 | }; 336 | rootObject = F28C7A211E314C71009059C5 /* Project object */; 337 | } 338 | -------------------------------------------------------------------------------- /MBDoubleSliderExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MBDoubleSliderExample.xcodeproj/xcuserdata/Viorel.xcuserdatad/xcschemes/MBDoubleSliderExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 69 | 70 | 74 | 75 | 76 | 77 | 78 | 79 | 85 | 87 | 93 | 94 | 95 | 96 | 98 | 99 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /MBDoubleSliderExample.xcodeproj/xcuserdata/Viorel.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MBDoubleSliderExample.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | F28C7A281E314C71009059C5 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /MBDoubleSliderExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MBDoubleSliderExample 4 | // 5 | // Created by Viorel Porumbescu on 19/01/2017. 6 | // Copyright © 2017 Viorel. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 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 | -------------------------------------------------------------------------------- /MBDoubleSliderExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /MBDoubleSliderExample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | Default 511 | 512 | 513 | 514 | 515 | 516 | 517 | Left to Right 518 | 519 | 520 | 521 | 522 | 523 | 524 | Right to Left 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | Default 536 | 537 | 538 | 539 | 540 | 541 | 542 | Left to Right 543 | 544 | 545 | 546 | 547 | 548 | 549 | Right to Left 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | -------------------------------------------------------------------------------- /MBDoubleSliderExample/Documents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c-Viorel/MBDoubleSliderExample/35093959145715cdc3c28be40cd6c9e6c7e92568/MBDoubleSliderExample/Documents.png -------------------------------------------------------------------------------- /MBDoubleSliderExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2017 Viorel. All rights reserved. 27 | NSMainStoryboardFile 28 | Main 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /MBDoubleSliderExample/MBCustomKnob.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MBCustomKnob.swift 3 | // customDoubleSlider 4 | // 5 | // Created by Viorel Porumbescu on 18/10/15. 6 | // Copyright (c) 2015 Viorel Porumbescu. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | 12 | @IBDesignable 13 | class MBCustomKnob: NSView { 14 | 15 | var color:NSColor = NSColor.white 16 | var borderColor: NSColor = NSColor.lightGray.withAlphaComponent(0.5) 17 | 18 | override func draw(_ dirtyRect: NSRect) { 19 | super.draw(dirtyRect) 20 | self.wantsLayer = true 21 | self.layer?.cornerRadius = self.frame.height / 2 22 | self.layer?.borderColor = borderColor.cgColor 23 | self.layer?.borderWidth = 1.2 24 | self.layer?.backgroundColor = color.cgColor 25 | self.layer?.masksToBounds = false 26 | 27 | 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /MBDoubleSliderExample/MBDoubleSlider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MBDoubleSlider.swift 3 | // customDoubleSlider 4 | // 5 | // Created by Viorel Porumbescu on 18/10/15. 6 | // Copyright (c) 2015 Viorel Porumbescu. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | protocol MBDoubleSliderDelegate { 12 | func controller(_ controller: MBDoubleSlider , didChangeFirstValue: CGFloat , secondValue: CGFloat) 13 | // TODO: shold be an optional protocol. 14 | // func controller(_ controller: MBDoubleSlider, willBeginChangeValues first:CGFloat, second:CGFloat) 15 | } 16 | 17 | @IBDesignable 18 | class MBDoubleSlider: NSControl { 19 | 20 | @IBInspectable var backgroundLineColor: NSColor = NSColor(red:0.780, green:0.780, blue:0.780, alpha:1) 21 | @IBInspectable var selectionLineColor: NSColor = NSColor(red:0.231, green:0.600, blue:0.988, alpha:1) 22 | @IBInspectable var textColor:NSColor = NSColor.gray {didSet {initViews()}} 23 | // TODO: Add option to hide info labels 24 | 25 | var minValue: CGFloat = 0 26 | var maxValue: CGFloat = 100 27 | 28 | var firstValue: CGFloat = 0 { didSet{ if firstValue < minValue || firstValue > secondValue { firstValue = minValue }; self.needsDisplay = true}} 29 | var secondValue: CGFloat = 100 { didSet{ if secondValue < firstValue || secondValue > maxValue { secondValue = maxValue } ; self.needsDisplay = true}} 30 | 31 | var minimValue:CGFloat = 10 32 | 33 | var delegate: MBDoubleSliderDelegate? 34 | 35 | // Custom Private Vars 36 | fileprivate var firstKnob:MBCustomKnob = MBCustomKnob() 37 | fileprivate var secondKnob:MBCustomKnob = MBCustomKnob() 38 | fileprivate var firstLabel:NSTextField = NSTextField(frame: NSMakeRect(0, -2, 30, 20)) 39 | fileprivate var secondLabel:NSTextField = NSTextField(frame: NSMakeRect(0, -2, 30, 20)) 40 | fileprivate var yOrigin:CGFloat = 0 41 | fileprivate var lineMaxWidh: CGFloat = 0 42 | fileprivate var shouldMoveFirst:Bool = false 43 | fileprivate var shouldMoveLast: Bool = false 44 | 45 | 46 | 47 | 48 | 49 | override init(frame frameRect: NSRect) { 50 | super.init(frame: frameRect) 51 | setUpLabels() 52 | // let shadow = NSShadow() 53 | // shadow.shadowColor = NSColor.black.withAlphaComponent(0.5) 54 | // shadow.shadowOffset = NSMakeSize(0.1, 0.1) 55 | // shadow.shadowBlurRadius = 3 56 | // 57 | // firstKnob.shadow = shadow 58 | // secondKnob.shadow = shadow 59 | } 60 | 61 | 62 | 63 | required init?(coder: NSCoder) { 64 | super.init(coder: coder) 65 | setUpLabels() 66 | // let shadow = NSShadow() 67 | // shadow.shadowColor = NSColor.black.withAlphaComponent(0.5) 68 | // shadow.shadowOffset = NSMakeSize(0.1, 0.1) 69 | // shadow.shadowBlurRadius = 3 70 | // 71 | // firstKnob.shadow = shadow 72 | // secondKnob.shadow = shadow 73 | } 74 | 75 | 76 | 77 | 78 | 79 | override func draw(_ dirtyRect: NSRect) { 80 | super.draw(dirtyRect) 81 | initViews() 82 | } 83 | 84 | 85 | 86 | var trackingArea:NSTrackingArea! 87 | override func updateTrackingAreas() { 88 | if trackingArea != nil { 89 | self.removeTrackingArea(trackingArea) 90 | } 91 | trackingArea = NSTrackingArea(rect: self.bounds, options: [NSTrackingAreaOptions.enabledDuringMouseDrag , NSTrackingAreaOptions.mouseMoved, NSTrackingAreaOptions.activeAlways, .cursorUpdate], owner: self, userInfo: nil) 92 | self.addTrackingArea(trackingArea) 93 | } 94 | 95 | 96 | 97 | 98 | /// Mouse down event : We test if current mouse location is inside of first or second knob. If yes, then we 99 | // tell that selected knob that it can be moved 100 | override func mouseDown(with theEvent: NSEvent) { 101 | let loc = self.convert(theEvent.locationInWindow, from: self.window?.contentView) 102 | 103 | if NSPointInRect(loc, firstKnob.frame) { 104 | shouldMoveFirst = true 105 | } 106 | if NSPointInRect(loc, secondKnob.frame) { 107 | shouldMoveLast = true 108 | } 109 | } 110 | 111 | // Mouse dragged Event : if is any selected knob we will move to new position, and we calculate 112 | // new the new slider values 113 | override func mouseDragged(with theEvent: NSEvent) { 114 | let loc = self.convert(theEvent.locationInWindow, from: self.window?.contentView) 115 | 116 | let _ = self.convert(theEvent.locationInWindow, to: self) 117 | 118 | if shouldMoveFirst { 119 | let minim = CGFloat(0) 120 | let maxim = secondKnob.frame.origin.x - firstKnob.frame.width - ((lineMaxWidh / maxValue) * minimValue ) 121 | if loc.x > minim && loc.x < maxim { 122 | firstValue = (loc.x * maxValue) / lineMaxWidh 123 | if loc.x < minim { 124 | firstValue = (minim * maxValue) / lineMaxWidh 125 | } 126 | if loc.x > maxim { 127 | firstValue = (maxim * maxValue) / lineMaxWidh 128 | } 129 | }else if loc.x < minim { 130 | firstValue = (minim * maxValue) / lineMaxWidh 131 | } 132 | self.needsDisplay = true 133 | create() 134 | } 135 | if shouldMoveLast { 136 | let minim = firstKnob.frame.origin.x + secondKnob.frame.width + ((lineMaxWidh / maxValue) * minimValue ) 137 | let maxim = lineMaxWidh 138 | if loc.x > minim && loc.x < maxim { 139 | secondValue = (loc.x * maxValue) / lineMaxWidh 140 | if loc.x < minim { 141 | secondValue = (minim * maxValue) / lineMaxWidh 142 | } 143 | if loc.x > maxim { 144 | secondValue = (maxim * maxValue) / lineMaxWidh 145 | } 146 | } else if loc.x > maxim { 147 | secondValue = (maxim * maxValue) / lineMaxWidh 148 | } 149 | self.needsDisplay = true 150 | create() 151 | } 152 | } 153 | 154 | //Mouse up event : We "deselect" both knobs. 155 | override func mouseUp(with theEvent: NSEvent) { 156 | shouldMoveLast = false 157 | shouldMoveFirst = false 158 | } 159 | 160 | func initViews() { 161 | let textOrigin: CGFloat = 0 162 | let knobSize = self.frame.height * 0.4 163 | lineMaxWidh = self.frame.width - knobSize 164 | yOrigin = self.frame.height * 0.6 165 | let firstX = (firstValue * lineMaxWidh) / maxValue 166 | let secondX = (secondValue * lineMaxWidh) / maxValue 167 | firstKnob.setFrameSize(NSMakeSize(knobSize, knobSize)) 168 | secondKnob.setFrameSize(NSMakeSize(knobSize, knobSize)) 169 | firstKnob.setFrameOrigin(NSMakePoint(firstX, yOrigin)) 170 | secondKnob.setFrameOrigin(NSMakePoint(secondX, yOrigin)) 171 | firstLabel.setFrameOrigin(NSMakePoint(firstX, textOrigin)) 172 | secondLabel.setFrameOrigin(NSMakePoint(secondX, textOrigin)) 173 | 174 | firstLabel.textColor = textColor 175 | secondLabel.textColor = textColor 176 | 177 | // Center text Label if is posible 178 | if firstX > 8 { 179 | firstLabel.setFrameOrigin(NSMakePoint(firstX - 8, textOrigin)) 180 | } 181 | if secondX < lineMaxWidh - 8 { 182 | secondLabel.setFrameOrigin(NSMakePoint(secondX - 8, textOrigin)) 183 | } 184 | 185 | if secondX > lineMaxWidh - 8 { 186 | secondLabel.setFrameOrigin(NSMakePoint(lineMaxWidh - 16, textOrigin)) 187 | } 188 | if (firstLabel.frame.origin.x + NSWidth(firstLabel.frame) ) > secondLabel.frame.origin.x { 189 | let size = (secondLabel.frame.origin.x - (firstLabel.frame.origin.x + NSWidth(firstLabel.frame) )) / 2 190 | var state = true 191 | if firstX < 8 { 192 | state = false 193 | secondLabel.setFrameOrigin(NSMakePoint(secondLabel.frame.origin.x - size - size, textOrigin)) 194 | } 195 | if secondX > lineMaxWidh - 8 { 196 | state = false 197 | firstLabel.setFrameOrigin(NSMakePoint(firstLabel.frame.origin.x + size + size, textOrigin)) 198 | } 199 | if state { 200 | firstLabel.setFrameOrigin(NSMakePoint(firstLabel.frame.origin.x + size, textOrigin)) 201 | secondLabel.setFrameOrigin(NSMakePoint(secondLabel.frame.origin.x - size, textOrigin)) 202 | } 203 | } 204 | firstLabel.stringValue = secondsToMinute(firstValue) 205 | secondLabel.stringValue = secondsToMinute(secondValue) 206 | // Draw background line 207 | let backgroundLine = NSBezierPath() 208 | backgroundLine.move(to: NSMakePoint(knobSize * 0.5, self.frame.height * 0.8)) 209 | backgroundLine.line(to: NSMakePoint(lineMaxWidh + knobSize * 0.5 , self.frame.height * 0.8)) 210 | backgroundLine.lineCapStyle = NSLineCapStyle.roundLineCapStyle 211 | backgroundLine.lineWidth = 3 212 | backgroundLineColor.set() 213 | backgroundLine.stroke() 214 | ///Draw selection line (the line between knobs) 215 | let selectionLine = NSBezierPath() 216 | selectionLine.move(to: NSMakePoint(firstX + knobSize / 2 , self.frame.height * 0.8)) 217 | selectionLine.line(to: NSMakePoint(secondX + knobSize / 2 , self.frame.height * 0.8)) 218 | selectionLine.lineCapStyle = NSLineCapStyle.roundLineCapStyle 219 | selectionLine.lineWidth = 3 220 | selectionLineColor.setStroke() 221 | selectionLine.stroke() 222 | 223 | Swift.print(self.subviews.count) 224 | self.addSubview(firstKnob) 225 | self.addSubview(secondKnob) 226 | self.addSubview(firstLabel) 227 | self.addSubview(secondLabel) 228 | } 229 | 230 | 231 | func setUpLabels(){ 232 | firstLabel.isBordered = false 233 | firstLabel.identifier = "10" 234 | firstLabel.isEditable = false 235 | firstLabel.isSelectable = false 236 | firstLabel.stringValue = "00:00" 237 | firstLabel.backgroundColor = NSColor.white.withAlphaComponent(0) 238 | firstLabel.font = NSFont(name: "HelveticaNeue", size: 10) 239 | firstLabel.textColor = NSColor.gray 240 | firstLabel.alignment = NSTextAlignment.left 241 | 242 | secondLabel.isBordered = false 243 | secondLabel.identifier = "10" 244 | secondLabel.isEditable = false 245 | secondLabel.isSelectable = false 246 | secondLabel.stringValue = "00:00" 247 | secondLabel.backgroundColor = NSColor.white.withAlphaComponent(0) 248 | secondLabel.font = NSFont(name: "HelveticaNeue", size: 10) 249 | secondLabel.textColor = NSColor.gray 250 | secondLabel.alignment = NSTextAlignment.left 251 | 252 | } 253 | 254 | /// If has a delegate we will send changed notification, and new values for slider. 255 | /// Also we trigger action if this control has one. 256 | func create() { 257 | if self.action != nil { 258 | NSApp.sendAction(self.action!, to: self.target, from: self) 259 | } 260 | 261 | if let delegate = self.delegate { 262 | delegate.controller(self, didChangeFirstValue: firstValue, secondValue: secondValue) 263 | } 264 | } 265 | 266 | /// Convert seconds to 00:00 format 267 | fileprivate func secondsToMinute(_ sec:CGFloat) -> String{ 268 | let intSec = Int(sec) 269 | let min = intSec / 60 270 | let remainS = intSec % 60 271 | let str = NSString(format: "%02d:%02d", min, remainS) 272 | return String(str) 273 | } 274 | 275 | } 276 | 277 | -------------------------------------------------------------------------------- /MBDoubleSliderExample/MBFilledView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MBFilledView.swift 3 | // iClock 4 | // 5 | // Created by Viorel Porumbescu on 23/02/16. 6 | // Copyright © 2016 Viorel Porumbescu. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @IBDesignable 12 | class MBFilledView: NSView { 13 | 14 | 15 | @IBInspectable var backgroundColor:NSColor = NSColor.black.withAlphaComponent(0.5) { didSet { self.needsDisplay = true} } 16 | /** 17 | This kind of view will lwt you to round 18 | */ 19 | @IBInspectable var cornerRadius: CGFloat = 5.0 { didSet { self.needsDisplay = true} } 20 | @IBInspectable var radiusTopLeft:Bool = true { didSet { self.needsDisplay = true} } 21 | @IBInspectable var radiusTopRight:Bool = true { didSet { self.needsDisplay = true} } 22 | @IBInspectable var radiusBottomLeft: Bool = true { didSet { self.needsDisplay = true} } 23 | @IBInspectable var radiusBottomRight:Bool = true { didSet { self.needsDisplay = true} } 24 | 25 | @IBInspectable var hasShadow: Bool = false { didSet { self.needsDisplay = true} } 26 | @IBInspectable var shadowColor:NSColor = NSColor.black { didSet { self.needsDisplay = true} } 27 | @IBInspectable var xShadowPosition:Int = 2 { didSet { self.needsDisplay = true} } 28 | @IBInspectable var yShadowPosition: Int = 1 { didSet { self.needsDisplay = true} } 29 | 30 | 31 | @IBInspectable var hasVisualEffectView:Bool = false { didSet{ self.setupLayers()}} 32 | @IBInspectable var materialView:Int = 9 33 | 34 | 35 | required init?(coder: NSCoder) { 36 | super.init(coder: coder) 37 | self.setupLayers() 38 | } 39 | 40 | 41 | override init(frame frameRect: NSRect) { 42 | super.init(frame: frameRect) 43 | self.setupLayers() 44 | } 45 | 46 | func setupLayers() { 47 | 48 | if self.hasVisualEffectView { 49 | let view = NSVisualEffectView(frame: self.bounds) 50 | view.identifier = "1112" 51 | view.blendingMode = NSVisualEffectBlendingMode.behindWindow 52 | view.state = NSVisualEffectState.active 53 | view.wantsLayer = true 54 | view.layer?.masksToBounds = true 55 | view.layer?.cornerRadius = cornerRadius 56 | if let met = NSVisualEffectMaterial(rawValue: materialView) { 57 | view.material = met 58 | } else { 59 | view.material = .ultraDark 60 | } 61 | self.addSubview(view, positioned: .below, relativeTo: nil) 62 | } else { 63 | for view in self.subviews { 64 | if view.identifier == "1112" { 65 | view.removeFromSuperview() 66 | } 67 | } 68 | } 69 | } 70 | 71 | 72 | override func draw(_ dirtyRect: NSRect) { 73 | super.draw(dirtyRect) 74 | // Configure layers & colors // 75 | if hasVisualEffectView { 76 | setupLayers() 77 | } 78 | self.wantsLayer = true 79 | // Configure shadow // 80 | if hasShadow { 81 | self.layer?.shadowOffset = NSSize(width: xShadowPosition, height: yShadowPosition) 82 | self.layer?.shadowOpacity = 0.85 83 | self.layer?.shadowColor = shadowColor.cgColor 84 | } 85 | self.layer?.backgroundColor = NSColor.clear.cgColor 86 | self.layer?.masksToBounds = true 87 | if radiusTopLeft && radiusTopRight && radiusBottomLeft && radiusBottomRight { 88 | self.layer?.cornerRadius = cornerRadius 89 | self.layer?.backgroundColor = backgroundColor.cgColor 90 | } else { 91 | 92 | //Set background color and radius 93 | let rectangleRect = self.frame 94 | let rectangleInnerRect = NSInsetRect(rectangleRect, cornerRadius, cornerRadius) 95 | let rectanglePath = NSBezierPath() 96 | radiusBottomLeft ? rectanglePath.appendArc(withCenter: NSMakePoint(NSMinX(rectangleInnerRect), NSMinY(rectangleInnerRect)), radius: cornerRadius, startAngle: 180, endAngle: 270) : rectanglePath.move(to: NSMakePoint(NSMinX(rectangleRect), NSMinY(rectangleRect))) 97 | radiusBottomRight ? rectanglePath.appendArc(withCenter: NSMakePoint(NSMaxX(rectangleInnerRect), NSMinY(rectangleInnerRect)), radius: cornerRadius, startAngle: 270, endAngle: 360) : rectanglePath.line(to: NSMakePoint(NSMaxX(rectangleRect), NSMinY(rectangleRect))) 98 | radiusTopRight ? rectanglePath.appendArc(withCenter: NSMakePoint(NSMaxX(rectangleInnerRect), NSMaxY(rectangleInnerRect)), radius: cornerRadius, startAngle: 0, endAngle: 90) : rectanglePath.line(to: NSMakePoint(NSMaxX(rectangleRect), NSMaxY(rectangleRect))) 99 | radiusTopLeft ? rectanglePath.appendArc(withCenter: NSMakePoint(NSMinX(rectangleInnerRect), NSMaxY(rectangleInnerRect)), radius: cornerRadius, startAngle: 90, endAngle: 180) : rectanglePath.line(to: NSMakePoint(NSMinX(rectangleRect), NSMaxY(rectangleRect))) 100 | rectanglePath.close() 101 | 102 | backgroundColor.set() 103 | rectanglePath.fill() 104 | } 105 | } 106 | 107 | 108 | 109 | } 110 | -------------------------------------------------------------------------------- /MBDoubleSliderExample/MBTransparentWindow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MBTransparentWindow.swift 3 | // iClock 4 | // 5 | // Created by Viorel Porumbescu on 23/02/16. 6 | // Copyright © 2016 Viorel Porumbescu. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class MBTransparentWindow: NSWindow { 12 | 13 | // Override init to make window completely transparent and movable by background 14 | override init(contentRect: NSRect, styleMask aStyle: NSWindowStyleMask, backing bufferingType: NSBackingStoreType, defer flag: Bool) { 15 | 16 | super.init(contentRect: contentRect, styleMask: aStyle, backing: bufferingType, defer: flag) 17 | self.backgroundColor = NSColor.clear 18 | self.level = 0 19 | self.isOpaque = false 20 | self.hasShadow = false 21 | self.titleVisibility = NSWindowTitleVisibility.hidden 22 | self.titlebarAppearsTransparent = true 23 | self.isMovableByWindowBackground = true 24 | self.standardWindowButton(NSWindowButton.miniaturizeButton)?.isHidden = true 25 | self.standardWindowButton(NSWindowButton.zoomButton)?.isHidden = true 26 | self.standardWindowButton(NSWindowButton.closeButton)?.isHidden = false 27 | } 28 | 29 | 30 | } 31 | 32 | -------------------------------------------------------------------------------- /MBDoubleSliderExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // MBDoubleSliderExample 4 | // 5 | // Created by Viorel Porumbescu on 19/01/2017. 6 | // Copyright © 2017 Viorel. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class ViewController: NSViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | // Do any additional setup after loading the view. 17 | } 18 | 19 | override var representedObject: Any? { 20 | didSet { 21 | // Update the view, if already loaded. 22 | } 23 | } 24 | 25 | 26 | } 27 | 28 | -------------------------------------------------------------------------------- /MBDoubleSliderExample/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c-Viorel/MBDoubleSliderExample/35093959145715cdc3c28be40cd6c9e6c7e92568/MBDoubleSliderExample/example.png -------------------------------------------------------------------------------- /MBDoubleSliderExample/ibInspectable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c-Viorel/MBDoubleSliderExample/35093959145715cdc3c28be40cd6c9e6c7e92568/MBDoubleSliderExample/ibInspectable.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MBDoubleSliderExample 2 | ![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg?style=flat) 3 | [![Swift](https://img.shields.io/badge/Swift-3.0-orange.svg)](https://swift.org) 4 | [![Xcode](https://img.shields.io/badge/Xcode-8.0-blue.svg)](https://developer.apple.com/xcode) 5 | 6 | CustomDoubleSlider for macOs written in swift 3.0 7 | 8 | A double slider uset to select a range. The range is specific for time, but can be adaptad to use anywhere. 9 | 10 | ![Alt text](https://github.com/c-Viorel/MBDoubleSliderExample/blob/master/MBDoubleSliderExample/example.png?raw=true) 11 | 12 | ## IBInspectable properties 13 | ![Alt text](https://github.com/c-Viorel/MBDoubleSliderExample/blob/master/MBDoubleSliderExample/ibInspectable.png?raw=true) 14 | 15 | ### Requirements 16 | - macOS 17 | - Xcode 8.0+ 18 | 19 | ## Developer website 20 | 21 | A team is created from the passion of a common goal or from the interest of profit. The passion will generate respect for what you believe in, but greed will lead to demagogy - the cancer of every group. We are an united team with the passion to create truly meaningful tools, perfected so that it will make every user feel he can do everything. Intuitive for a beginner, complete for professionals. 22 | 23 |   [Minglebit website](https://minglebit.com) 24 | 25 |  [OnTime PRO-Ultimate time tool](https://itunes.apple.com/us/app/iclock-pro-the-ultimate-time-tool/id1244748504?ls=1&mt=12) 26 | 27 | --------------------------------------------------------------------------------