commit ed4077e9a8d0cacde53b6c004ed2d30d7a0338da Author: Louis D'hauwe Date: Mon Apr 2 15:09:23 2018 +0200 Initial commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..edf70ca --- /dev/null +++ b/.editorconfig @@ -0,0 +1,5 @@ +root = true + +[*] +indent_style = tab +indent_size = 4 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..25d95bb --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# Xcode +# +.DS_Store +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate + diff --git a/.swift-version b/.swift-version new file mode 100755 index 0000000..389f774 --- /dev/null +++ b/.swift-version @@ -0,0 +1 @@ +4.0 \ No newline at end of file diff --git a/HueKit.xcodeproj/project.pbxproj b/HueKit.xcodeproj/project.pbxproj new file mode 100644 index 0000000..d204a16 --- /dev/null +++ b/HueKit.xcodeproj/project.pbxproj @@ -0,0 +1,397 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + BE5C1E1C1F3266B600B0080C /* HueKit.h in Headers */ = {isa = PBXBuildFile; fileRef = BE5C1E1A1F3266B600B0080C /* HueKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BE5C1E361F326E2500B0080C /* RGB.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE5C1E351F326E2500B0080C /* RGB.swift */; }; + BE5C1E381F326E3300B0080C /* HSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE5C1E371F326E3300B0080C /* HSV.swift */; }; + BE5C1E3B1F326EE600B0080C /* UIColor+Values.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE5C1E3A1F326EE600B0080C /* UIColor+Values.swift */; }; + BE5C1E3D1F3270CE00B0080C /* CGFloat+Pin.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE5C1E3C1F3270CE00B0080C /* CGFloat+Pin.swift */; }; + BE5C1E3F1F32710B00B0080C /* HSBComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE5C1E3E1F32710B00B0080C /* HSBComponent.swift */; }; + BE5C1E471F3271D600B0080C /* ColorBarPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE5C1E401F3271D600B0080C /* ColorBarPicker.swift */; }; + BE5C1E481F3271D600B0080C /* ColorBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE5C1E411F3271D600B0080C /* ColorBarView.swift */; }; + BE5C1E491F3271D600B0080C /* ColorIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE5C1E421F3271D600B0080C /* ColorIndicatorView.swift */; }; + BE5C1E4A1F3271D600B0080C /* ColorSquarePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE5C1E431F3271D600B0080C /* ColorSquarePicker.swift */; }; + BE5C1E4B1F3271D600B0080C /* ColorSquareView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE5C1E441F3271D600B0080C /* ColorSquareView.swift */; }; + BE5C1E4D1F3271D600B0080C /* SourceColorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE5C1E461F3271D600B0080C /* SourceColorView.swift */; }; + BE5C1E4F1F3271E900B0080C /* HSBGen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE5C1E4E1F3271E900B0080C /* HSBGen.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + BE5C1E171F3266B600B0080C /* HueKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = HueKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + BE5C1E1A1F3266B600B0080C /* HueKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HueKit.h; sourceTree = ""; }; + BE5C1E1B1F3266B600B0080C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + BE5C1E351F326E2500B0080C /* RGB.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RGB.swift; path = Model/RGB.swift; sourceTree = ""; }; + BE5C1E371F326E3300B0080C /* HSV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HSV.swift; path = Model/HSV.swift; sourceTree = ""; }; + BE5C1E3A1F326EE600B0080C /* UIColor+Values.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIColor+Values.swift"; path = "Util/UIColor+Values.swift"; sourceTree = ""; }; + BE5C1E3C1F3270CE00B0080C /* CGFloat+Pin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "CGFloat+Pin.swift"; path = "Util/CGFloat+Pin.swift"; sourceTree = ""; }; + BE5C1E3E1F32710B00B0080C /* HSBComponent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HSBComponent.swift; path = Model/HSBComponent.swift; sourceTree = ""; }; + BE5C1E401F3271D600B0080C /* ColorBarPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ColorBarPicker.swift; path = View/ColorBarPicker.swift; sourceTree = ""; }; + BE5C1E411F3271D600B0080C /* ColorBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ColorBarView.swift; path = View/ColorBarView.swift; sourceTree = ""; }; + BE5C1E421F3271D600B0080C /* ColorIndicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ColorIndicatorView.swift; path = View/ColorIndicatorView.swift; sourceTree = ""; }; + BE5C1E431F3271D600B0080C /* ColorSquarePicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ColorSquarePicker.swift; path = View/ColorSquarePicker.swift; sourceTree = ""; }; + BE5C1E441F3271D600B0080C /* ColorSquareView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ColorSquareView.swift; path = View/ColorSquareView.swift; sourceTree = ""; }; + BE5C1E461F3271D600B0080C /* SourceColorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SourceColorView.swift; path = View/SourceColorView.swift; sourceTree = ""; }; + BE5C1E4E1F3271E900B0080C /* HSBGen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HSBGen.swift; path = Util/HSBGen.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + BE5C1E131F3266B600B0080C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + BE5C1E0D1F3266B600B0080C = { + isa = PBXGroup; + children = ( + BE5C1E191F3266B600B0080C /* HueKit */, + BE5C1E181F3266B600B0080C /* Products */, + ); + sourceTree = ""; + }; + BE5C1E181F3266B600B0080C /* Products */ = { + isa = PBXGroup; + children = ( + BE5C1E171F3266B600B0080C /* HueKit.framework */, + ); + name = Products; + sourceTree = ""; + }; + BE5C1E191F3266B600B0080C /* HueKit */ = { + isa = PBXGroup; + children = ( + BE5C1E391F326E9300B0080C /* Util */, + BE5C1E221F326BF500B0080C /* Model */, + BE5C1E231F326BFD00B0080C /* View */, + BE5C1E1A1F3266B600B0080C /* HueKit.h */, + BE5C1E1B1F3266B600B0080C /* Info.plist */, + ); + path = HueKit; + sourceTree = ""; + }; + BE5C1E221F326BF500B0080C /* Model */ = { + isa = PBXGroup; + children = ( + BE5C1E351F326E2500B0080C /* RGB.swift */, + BE5C1E371F326E3300B0080C /* HSV.swift */, + BE5C1E3E1F32710B00B0080C /* HSBComponent.swift */, + ); + name = Model; + sourceTree = ""; + }; + BE5C1E231F326BFD00B0080C /* View */ = { + isa = PBXGroup; + children = ( + BE5C1E401F3271D600B0080C /* ColorBarPicker.swift */, + BE5C1E411F3271D600B0080C /* ColorBarView.swift */, + BE5C1E421F3271D600B0080C /* ColorIndicatorView.swift */, + BE5C1E431F3271D600B0080C /* ColorSquarePicker.swift */, + BE5C1E441F3271D600B0080C /* ColorSquareView.swift */, + BE5C1E461F3271D600B0080C /* SourceColorView.swift */, + ); + name = View; + sourceTree = ""; + }; + BE5C1E391F326E9300B0080C /* Util */ = { + isa = PBXGroup; + children = ( + BE5C1E4E1F3271E900B0080C /* HSBGen.swift */, + BE5C1E3A1F326EE600B0080C /* UIColor+Values.swift */, + BE5C1E3C1F3270CE00B0080C /* CGFloat+Pin.swift */, + ); + name = Util; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + BE5C1E141F3266B600B0080C /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + BE5C1E1C1F3266B600B0080C /* HueKit.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + BE5C1E161F3266B600B0080C /* HueKit */ = { + isa = PBXNativeTarget; + buildConfigurationList = BE5C1E1F1F3266B600B0080C /* Build configuration list for PBXNativeTarget "HueKit" */; + buildPhases = ( + BE5C1E121F3266B600B0080C /* Sources */, + BE5C1E131F3266B600B0080C /* Frameworks */, + BE5C1E141F3266B600B0080C /* Headers */, + BE5C1E151F3266B600B0080C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = HueKit; + productName = HueKit; + productReference = BE5C1E171F3266B600B0080C /* HueKit.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + BE5C1E0E1F3266B600B0080C /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0900; + ORGANIZATIONNAME = "Silver Fox"; + TargetAttributes = { + BE5C1E161F3266B600B0080C = { + CreatedOnToolsVersion = 8.3.3; + DevelopmentTeam = 6G5LMQ72D8; + LastSwiftMigration = 0900; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = BE5C1E111F3266B600B0080C /* Build configuration list for PBXProject "HueKit" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = BE5C1E0D1F3266B600B0080C; + productRefGroup = BE5C1E181F3266B600B0080C /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + BE5C1E161F3266B600B0080C /* HueKit */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + BE5C1E151F3266B600B0080C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + BE5C1E121F3266B600B0080C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BE5C1E3B1F326EE600B0080C /* UIColor+Values.swift in Sources */, + BE5C1E471F3271D600B0080C /* ColorBarPicker.swift in Sources */, + BE5C1E4F1F3271E900B0080C /* HSBGen.swift in Sources */, + BE5C1E4D1F3271D600B0080C /* SourceColorView.swift in Sources */, + BE5C1E4A1F3271D600B0080C /* ColorSquarePicker.swift in Sources */, + BE5C1E491F3271D600B0080C /* ColorIndicatorView.swift in Sources */, + BE5C1E381F326E3300B0080C /* HSV.swift in Sources */, + BE5C1E3F1F32710B00B0080C /* HSBComponent.swift in Sources */, + BE5C1E361F326E2500B0080C /* RGB.swift in Sources */, + BE5C1E4B1F3271D600B0080C /* ColorSquareView.swift in Sources */, + BE5C1E3D1F3270CE00B0080C /* CGFloat+Pin.swift in Sources */, + BE5C1E481F3271D600B0080C /* ColorBarView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + BE5C1E1D1F3266B600B0080C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + BE5C1E1E1F3266B600B0080C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + BE5C1E201F3266B600B0080C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6G5LMQ72D8; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = HueKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = be.silverfox.HueKit; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.0; + }; + name = Debug; + }; + BE5C1E211F3266B600B0080C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6G5LMQ72D8; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = HueKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = be.silverfox.HueKit; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + BE5C1E111F3266B600B0080C /* Build configuration list for PBXProject "HueKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BE5C1E1D1F3266B600B0080C /* Debug */, + BE5C1E1E1F3266B600B0080C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + BE5C1E1F1F3266B600B0080C /* Build configuration list for PBXNativeTarget "HueKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BE5C1E201F3266B600B0080C /* Debug */, + BE5C1E211F3266B600B0080C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = BE5C1E0E1F3266B600B0080C /* Project object */; +} diff --git a/HueKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/HueKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..4b72f4f --- /dev/null +++ b/HueKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/HueKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/HueKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/HueKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/HueKit/HueKit.h b/HueKit/HueKit.h new file mode 100644 index 0000000..89154bf --- /dev/null +++ b/HueKit/HueKit.h @@ -0,0 +1,7 @@ +// +// HueKit.h +// HueKit +// +// Created by Louis D'hauwe on 02/08/2017. +// Copyright © 2017 Silver Fox. All rights reserved. +// diff --git a/HueKit/Info.plist b/HueKit/Info.plist new file mode 100644 index 0000000..fbe1e6b --- /dev/null +++ b/HueKit/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/HueKit/Model/HSBComponent.swift b/HueKit/Model/HSBComponent.swift new file mode 100644 index 0000000..dc24040 --- /dev/null +++ b/HueKit/Model/HSBComponent.swift @@ -0,0 +1,15 @@ +// +// HSBComponent.swift +// HueKit +// +// Created by Louis D'hauwe on 02/08/2017. +// Copyright © 2017 Silver Fox. All rights reserved. +// + +import Foundation + +enum HSBComponent: Int { + case hue = 0 + case saturation = 1 + case brightness = 2 +} diff --git a/HueKit/Model/HSV.swift b/HueKit/Model/HSV.swift new file mode 100644 index 0000000..f88c4e6 --- /dev/null +++ b/HueKit/Model/HSV.swift @@ -0,0 +1,97 @@ +// +// HSV.swift +// HueKit +// +// Created by Louis D'hauwe on 02/08/2017. +// Copyright © 2017 Silver Fox. All rights reserved. +// + +import Foundation +import CoreGraphics + +public struct HSV { + /// In degrees (range 0...360) + public var h: CGFloat + + /// Percentage in range 0...1 + public var s: CGFloat + + /// Percentage in range 0...1 + /// Also known as "brightness" (B) + public var v: CGFloat +} + +extension HSV { + + /// These functions convert between an RGB value with components in the + /// 0.0..1.0 range to HSV where Hue is 0 .. 360 and Saturation and + /// Value (aka Brightness) are percentages expressed as 0.0..1.0. + // + /// Note that HSB (B = Brightness) and HSV (V = Value) are interchangeable + /// names that mean the same thing. I use V here as it is unambiguous + /// relative to the B in RGB, which is Blue. + func toRGB() -> RGB { + + var rgb = self.hueToRGB() + + let c = v * s + let m = v - c + + rgb.r = rgb.r * c + m + rgb.g = rgb.g * c + m + rgb.b = rgb.b * c + m + + return rgb + } + + func hueToRGB() -> RGB { + + let hPrime = h / 60.0 + + let x = 1.0 - abs(hPrime.truncatingRemainder(dividingBy: 2.0) - 1.0) + + let r: CGFloat + let g: CGFloat + let b: CGFloat + + if hPrime < 1.0 { + + r = 1 + g = x + b = 0 + + } else if hPrime < 2.0 { + + r = x + g = 1 + b = 0 + + } else if hPrime < 3.0 { + + r = 0 + g = 1 + b = x + + } else if hPrime < 4.0 { + + r = 0 + g = x + b = 1 + + } else if hPrime < 5.0 { + + r = x + g = 0 + b = 1 + + } else { + + r = 1 + g = 0 + b = x + + } + + return RGB(r: r, g: g, b: b) + } +} diff --git a/HueKit/Model/RGB.swift b/HueKit/Model/RGB.swift new file mode 100644 index 0000000..bde7d46 --- /dev/null +++ b/HueKit/Model/RGB.swift @@ -0,0 +1,114 @@ +// +// RGB.swift +// HueKit +// +// Created by Louis D'hauwe on 02/08/2017. +// Copyright © 2017 Silver Fox. All rights reserved. +// + +import Foundation +import CoreGraphics + +public struct RGB { + /// In range 0...1 + public var r: CGFloat + + /// In range 0...1 + public var g: CGFloat + + /// In range 0...1 + public var b: CGFloat +} + +public extension RGB { + + func toHSV(preserveHS: Bool, h: CGFloat = 0, s: CGFloat = 0) -> HSV { + + var h = h + var s = s + var v: CGFloat = 0 + + var max = r + + if max < g { + max = g + } + + if max < b { + max = b + } + + var min = r + + if min > g { + min = g + } + + if min > b { + min = b + } + + // Brightness (aka Value) + + v = max + + // Saturation + + var sat: CGFloat = 0.0 + + if max != 0.0 { + + sat = (max - min) / max + s = sat + + } else { + + sat = 0.0 + + // Black, so sat is undefined, use 0 + if !preserveHS { + s = 0.0 + } + } + + // Hue + + var delta: CGFloat = 0 + + if sat == 0.0 { + + // No color, so hue is undefined, use 0 + if !preserveHS { + h = 0.0 + } + + } else { + + delta = max - min + + var hue: CGFloat = 0 + + if r == max { + hue = (g - b) / delta + } else if g == max { + hue = 2 + (b - r) / delta + } else { + hue = 4 + (r - g) / delta + } + + hue /= 6.0 + + if hue < 0.0 { + hue += 1.0 + } + + // 0.0 and 1.0 hues are actually both the same (red) + if !preserveHS || abs(hue - h) != 1.0 { + h = hue + } + } + + return HSV(h: h, s: s, v: v) + } + +} diff --git a/HueKit/Util/CGFloat+Pin.swift b/HueKit/Util/CGFloat+Pin.swift new file mode 100644 index 0000000..cd024f8 --- /dev/null +++ b/HueKit/Util/CGFloat+Pin.swift @@ -0,0 +1,25 @@ +// +// CGFloat+Pin.swift +// HueKit +// +// Created by Louis D'hauwe on 02/08/2017. +// Copyright © 2017 Silver Fox. All rights reserved. +// + +import Foundation +import CoreGraphics + +extension CGFloat { + + func pinned(between minValue: CGFloat, and maxValue: CGFloat) -> CGFloat { + + if self < minValue { + return minValue + } else if self > maxValue { + return maxValue + } else { + return self + } + } + +} diff --git a/HueKit/Util/HSBGen.swift b/HueKit/Util/HSBGen.swift new file mode 100644 index 0000000..d0bf02a --- /dev/null +++ b/HueKit/Util/HSBGen.swift @@ -0,0 +1,143 @@ +// +// HSBGen.swift +// HueKit +// +// Created by Louis D'hauwe on 29/07/2017. +// Copyright © 2017 Silver Fox. All rights reserved. +// + +import Foundation +import CoreGraphics + +class HSBGen { + + static func createBGRxImageContext(w: Int, h: Int) -> CGContext? { + + let colorSpace = CGColorSpaceCreateDeviceRGB() + + // BGRA is the most efficient on the iPhone. + var bitmapInfo = CGBitmapInfo(rawValue: CGImageByteOrderInfo.order32Little.rawValue) + + let noneSkipFirst = CGBitmapInfo(rawValue: CGImageAlphaInfo.noneSkipFirst.rawValue) + + bitmapInfo.formUnion(noneSkipFirst) + + let context = CGContext(data: nil, width: w, height: h, bitsPerComponent: 8, bytesPerRow: w * 4, space: colorSpace, bitmapInfo: bitmapInfo.rawValue) + + return context + } + + /// Generates an image where the specified barComponentIndex (0=H, 1=S, 2=V) + /// varies across the x-axis of the 256x1 pixel image and the other components + /// remain at the constant value specified in the hsv array. + static func createHSVBarContentImage(hsbComponent: HSBComponent, hsv: [CGFloat]) -> CGImage? { + + var hsv = hsv + + guard let context = createBGRxImageContext(w: 256, h: 1) else { + return nil + } + + guard var ptr = context.data?.assumingMemoryBound(to: UInt8.self) else { + return nil + } + + for x in 0..<256 { + + hsv[hsbComponent.rawValue] = CGFloat(x) / 255.0 + + let hsvVal = HSV(h: hsv[0] * 360.0, s: hsv[1], v: hsv[2]) + + let rgb = hsvVal.toRGB() + + ptr[0] = UInt8(rgb.b * 255.0) + ptr[1] = UInt8(rgb.g * 255.0) + ptr[2] = UInt8(rgb.r * 255.0) + + ptr = ptr.advanced(by: 4) + } + + let image = context.makeImage() + + return image + } + + static private func blend(_ value: UInt, _ percentIn255: UInt) -> UInt { + return (value) * (percentIn255) / 255 + } + + // Generates a 256x256 image with the specified constant hue where the + // Saturation and value vary in the X and Y axes respectively. + static func createSaturationBrightnessSquareContentImageWithHue(hue: CGFloat) -> CGImage? { + + guard let context = createBGRxImageContext(w: 256, h: 256) else { + return nil + } + + guard var dataPtr = context.data?.assumingMemoryBound(to: UInt8.self) else { + return nil + } + + let rowBytes = context.bytesPerRow + + let hsv = HSV(h: hue, s: 0, v: 0) + let rgb = hsv.hueToRGB() + + let r = rgb.r + let g = rgb.g + let b = rgb.b + + let r_s = (UInt) ((1.0 - r) * 255) + let g_s = (UInt) ((1.0 - g) * 255) + let b_s = (UInt) ((1.0 - b) * 255) + + // This doesn't use Swift ranges because those are pretty slow in debug builds + + var s: UInt = 0 + + while true { + + var ptr = dataPtr + + let r_hs: UInt = 255 - blend(s, r_s) + let g_hs: UInt = 255 - blend(s, g_s) + let b_hs: UInt = 255 - blend(s, b_s) + + var v: UInt = 255 + + while true { + + // Really, these should all be of the form used in blend(), + // which does a divide by 255. However, integer divide is + // implemented in software on ARM, so a divide by 256 + // (done as a bit shift) will be *nearly* the same value, + // and is faster. The more-accurate versions would look like: + // ptr[0] = blend(v, b_hs); + + ptr[0] = UInt8((v * b_hs) >> 8) + ptr[1] = UInt8((v * g_hs) >> 8) + ptr[2] = UInt8((v * r_hs) >> 8) + + ptr = ptr.advanced(by: rowBytes) + + if v == 0 { + break + } + + v -= 1 + } + + dataPtr = dataPtr.advanced(by: 4) + + if s == 255 { + break + } + + s += 1 + } + + let image = context.makeImage() + + return image + } +} diff --git a/HueKit/Util/UIColor+Values.swift b/HueKit/Util/UIColor+Values.swift new file mode 100644 index 0000000..0951c60 --- /dev/null +++ b/HueKit/Util/UIColor+Values.swift @@ -0,0 +1,57 @@ +// +// UIColor+Values.swift +// HueKit +// +// Created by Louis D'hauwe on 02/08/2017. +// Copyright © 2017 Silver Fox. All rights reserved. +// + +import Foundation +import UIKit + +extension UIColor { + + public var rgbValue: RGB? { + + guard let components = cgColor.components else { + return nil + } + + let numComponents = cgColor.numberOfComponents + + let r: CGFloat + let g: CGFloat + let b: CGFloat + + if numComponents < 3 { + r = components[0] + g = components[0] + b = components[0] + } else { + r = components[0] + g = components[1] + b = components[2] + } + + return RGB(r: r, g: g, b: b) + } + + public var hsvValue: HSV? { + + guard let rgb = rgbValue else { + return nil + } + + return rgb.toHSV(preserveHS: true) + } + + public func hsvValue(preservingHue hue: CGFloat, preservingSat sat: CGFloat) -> HSV? { + + guard let rgb = rgbValue else { + return nil + } + + return rgb.toHSV(preserveHS: true, h: hue, s: sat) + } + +} diff --git a/HueKit/View/ColorBarPicker.swift b/HueKit/View/ColorBarPicker.swift new file mode 100644 index 0000000..71c4ab5 --- /dev/null +++ b/HueKit/View/ColorBarPicker.swift @@ -0,0 +1,297 @@ +// +// ColorBarPicker.swift +// HueKit +// +// Created by Louis D'hauwe on 30/07/2017. +// Copyright © 2017 Silver Fox. All rights reserved. +// + +import Foundation +import UIKit + +@IBDesignable +public class ColorBarPicker: UIControl { + + private var isVertical: Bool = false { + didSet { + if isVertical != oldValue { + updateOrientation() + } + } + } + + private func updateVerticalState() { + +// let bounds = self.layer.presentation()?.bounds ?? self.bounds + isVertical = bounds.height > bounds.width + + } + + + private let contentInset: CGFloat = 20.0 + private static let indicatorSizeInactive = CGSize(width: 24.0, height: 24.0) + private static let indicatorSizeActive = CGSize(width: 40.0, height: 40.0) + + @IBInspectable + public var hue: CGFloat { + get { + if isVertical { + return 1.0 - value + } else { + return value + } + } + set { + if isVertical { + value = 1.0 - newValue + } else { + value = newValue + } + } + } + + private var value: CGFloat = 0.0 { + didSet { + + if oldValue != value { + + self.sendActions(for: .valueChanged) + self.setNeedsLayout() + } + + } + } + + private lazy var colorBarView: ColorBarView = { + return ColorBarView() + }() + + private lazy var indicator: ColorIndicatorView = { + + let frame = CGRect(origin: .zero, size: ColorBarPicker.indicatorSizeInactive) + let indicator = ColorIndicatorView(frame: frame) + + return indicator + + }() + + func updateOrientation() { + + guard colorBarView.superview != nil else { + return + } + + if isVertical { + + colorBarView.transform = .identity + + var rect = self.bounds + rect.size.width = bounds.height - contentInset * 2 + rect.size.height = bounds.width + + colorBarView.frame = rect + + colorBarView.transform = CGAffineTransform(rotationAngle: -.pi / 2.0) + + colorBarView.frame.origin = CGPoint(x: 0, y: contentInset) + + } else { + + var rect = self.bounds + rect.size.width -= contentInset * 2 + + colorBarView.frame = rect + + colorBarView.transform = .identity + + colorBarView.frame.origin = CGPoint(x: contentInset, y: 0) + + } + + } + + // MARK: - Drawing + + override public func layoutSubviews() { + + if colorBarView.superview == nil { + + colorBarView.isUserInteractionEnabled = false + + colorBarView.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(colorBarView) + + updateOrientation() + + } + + if indicator.superview == nil { + self.addSubview(indicator) + } + + let wasVertical = isVertical + + updateVerticalState() + + if wasVertical != isVertical { + + value = 1.0 - value + + } + + updateOrientation() +// colorBarView.setNeedsDisplay() + + indicator.color = UIColor(hue: hue, + saturation: 1.0, + brightness: 1.0, + alpha: 1.0) + + if isVertical { + + let indicatorLoc = contentInset + (self.value * (self.bounds.size.height - 2 * contentInset)) + indicator.center = CGPoint(x: self.bounds.midX, y: indicatorLoc) + + } else { + + let indicatorLoc = contentInset + (self.value * (self.bounds.size.width - 2 * contentInset)) + indicator.center = CGPoint(x: indicatorLoc, y: self.bounds.midY) + + } + + } + + // MARK: - Tracking + + private func trackIndicator(with touch: UITouch) { + + let touchLocation = touch.location(in: self) + + let percent: CGFloat + + if isVertical { + + percent = (touchLocation.y - contentInset) / (self.bounds.size.height - 2 * contentInset) + + } else { + + percent = (touchLocation.x - contentInset) / (self.bounds.size.width - 2 * contentInset) + + } + + self.value = percent.pinned(between: 0, and: 1) + } + + override public func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + self.trackIndicator(with: touch) + + growIndicator() + return true + } + + override public func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + self.trackIndicator(with: touch) + + return true + } + + override public func endTracking(_ touch: UITouch?, with event: UIEvent?) { + super.endTracking(touch, with: event) + + shrinkIndicator() + } + + override public func cancelTracking(with event: UIEvent?) { + super.cancelTracking(with: event) + + shrinkIndicator() + } + + private func changeIndicatorSize(to size: CGSize) { + + let center = self.indicator.center + + let indicatorRect = CGRect(origin: .zero, size: size) + + self.indicator.frame = indicatorRect + self.indicator.center = center + + } + + private func growIndicator() { + + UIView.animate(withDuration: 0.15, delay: 0.0, options: [.curveEaseIn], animations: { + + self.changeIndicatorSize(to: ColorBarPicker.indicatorSizeActive) + + }) { (finished) in + + } + + } + + private func shrinkIndicator() { + + UIView.animate(withDuration: 0.15, delay: 0.0, options: [.curveEaseOut], animations: { + + self.changeIndicatorSize(to: ColorBarPicker.indicatorSizeInactive) + self.indicator.setNeedsDisplay() + + }) { (finished) in + + self.indicator.setNeedsDisplay() + + } + + } + + + // MARK: - Accessibility + + private let accessibilityInterval: CGFloat = 0.05 + + public override var accessibilityTraits: UIAccessibilityTraits { + get { + var t = super.accessibilityTraits + + t |= UIAccessibilityTraitAdjustable + + return t + } + set { + super.accessibilityTraits = newValue + } + } + + public override func accessibilityIncrement() { + + var newValue = self.value + accessibilityInterval + + if newValue > 1.0 { + newValue -= 1.0 + } + + self.value = newValue + } + + public override func accessibilityDecrement() { + + var newValue = self.value - accessibilityInterval + + if newValue < 0 { + newValue += 1.0 + } + + self.value = newValue + } + + public override var accessibilityValue: String? { + get { + return String(format: "%d degrees hue", (self.value * 360.0)) + } + set { + super.accessibilityValue = newValue + } + } + +} diff --git a/HueKit/View/ColorBarView.swift b/HueKit/View/ColorBarView.swift new file mode 100644 index 0000000..f7c7bc6 --- /dev/null +++ b/HueKit/View/ColorBarView.swift @@ -0,0 +1,36 @@ +// +// ColorBarView.swift +// HueKit +// +// Created by Louis D'hauwe on 29/07/2017. +// Copyright © 2017 Silver Fox. All rights reserved. +// + +import Foundation +import UIKit + +@IBDesignable +class ColorBarView: UIView { + + private static func createContentImage() -> CGImage? { + + let hsv: [CGFloat] = [0.0, 1.0, 1.0] + + return HSBGen.createHSVBarContentImage(hsbComponent: .hue, hsv: hsv) + } + + override func draw(_ rect: CGRect) { + + guard let context = UIGraphicsGetCurrentContext() else { + return + } + + guard let image = ColorBarView.createContentImage() else { + return + } + + context.draw(image, in: self.bounds) + + } + +} diff --git a/HueKit/View/ColorIndicatorView.swift b/HueKit/View/ColorIndicatorView.swift new file mode 100644 index 0000000..531b4d0 --- /dev/null +++ b/HueKit/View/ColorIndicatorView.swift @@ -0,0 +1,69 @@ +// +// ColorIndicatorView.swift +// HueKit +// +// Created by Louis D'hauwe on 30/07/2017. +// Copyright © 2017 Silver Fox. All rights reserved. +// + +import Foundation +import UIKit + +@IBDesignable +public class ColorIndicatorView: UIView { + + @IBInspectable + public var color: UIColor = .black { + didSet { + + if oldValue != color { + self.setNeedsDisplay() + } + + } + } + + override public init(frame: CGRect) { + super.init(frame: frame) + + self.isOpaque = false + self.isUserInteractionEnabled = false + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public func draw(_ rect: CGRect) { + + guard let context = UIGraphicsGetCurrentContext() else { + return + } + + let center = CGPoint(x: self.bounds.midX, y: self.bounds.midY) + let radius = self.bounds.midX + + // Fill it: + + context.addArc(center: center, radius: radius - 1.0, startAngle: 0.0, endAngle: 2.0 * .pi, clockwise: true) + self.color.setFill() + context.fillPath() + + // Stroke it (black transucent, inner): + + context.addArc(center: center, radius: radius - 1.0, startAngle: 0.0, endAngle: 2.0 * .pi, clockwise: true) + + context.setStrokeColor(gray: 0.0, alpha: 0.5) + context.setLineWidth(2.0) + context.strokePath() + + // Stroke it (white, outer): + + context.addArc(center: center, radius: radius - 2.0, startAngle: 0.0, endAngle: 2.0 * .pi, clockwise: true) + + context.setStrokeColor(gray: 1.0, alpha: 1.0) + context.setLineWidth(2.0) + context.strokePath() + } + +} diff --git a/HueKit/View/ColorSquarePicker.swift b/HueKit/View/ColorSquarePicker.swift new file mode 100644 index 0000000..55d0002 --- /dev/null +++ b/HueKit/View/ColorSquarePicker.swift @@ -0,0 +1,170 @@ +// +// ColorSquarePicker.swift +// HueKit +// +// Created by Louis D'hauwe on 30/07/2017. +// Copyright © 2017 Silver Fox. All rights reserved. +// + +import Foundation +import UIKit + +@IBDesignable +public class ColorSquarePicker: UIControl { + + private let contentInsetX: CGFloat = 20 + private let contentInsetY: CGFloat = 20 + + private let indicatorSizeInactive: CGFloat = 24 + private let indicatorSizeActive: CGFloat = 40 + + private lazy var colorSquareView: ColorSquareView = { + return ColorSquareView() + }() + + private lazy var indicator: ColorIndicatorView = { + + let size = CGSize(width: self.indicatorSizeInactive, height: self.indicatorSizeInactive) + let indicatorRect = CGRect(origin: .zero, size: size) + + return ColorIndicatorView(frame: indicatorRect) + }() + + @IBInspectable + public var hue: CGFloat = 0.0 { + didSet { + if oldValue != hue { + self.setIndicatorColor() + } + } + } + + @IBInspectable + public var value: CGPoint = .zero { + didSet { + if oldValue != value { + + self.sendActions(for: .valueChanged) + self.setNeedsLayout() + } + } + } + + private func setIndicatorColor() { + + colorSquareView.hue = hue + indicator.color = UIColor(hue: hue, saturation: value.x, brightness: value.y, alpha: 1.0) + } + + public override func layoutSubviews() { + + if colorSquareView.superview == nil { + + colorSquareView.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(colorSquareView) + + colorSquareView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: contentInsetX).isActive = true + colorSquareView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -contentInsetX).isActive = true + colorSquareView.topAnchor.constraint(equalTo: self.topAnchor, constant: contentInsetY).isActive = true + colorSquareView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -contentInsetY).isActive = true + + } + + if indicator.superview == nil { + self.addSubview(indicator) + } + + self.setIndicatorColor() + + let indicatorX = contentInsetX + (self.value.x * (self.bounds.size.width - 2 * contentInsetX)) + let indicatorY = self.bounds.size.height - contentInsetY - (self.value.y * (self.bounds.size.height - 2 * contentInsetY)) + + indicator.center = CGPoint(x: indicatorX, y: indicatorY) + } + + // MARK: - Tracking + + private func trackIndicator(with touch: UITouch) { + let bounds = self.bounds + + var touchValue = CGPoint(x: 0, y: 0) + + touchValue.x = (touch.location(in: self).x - contentInsetX) / (bounds.size.width - 2 * contentInsetX) + + touchValue.y = (touch.location(in: self).y - contentInsetY) / (bounds.size.height - 2 * contentInsetY) + + + touchValue.x = touchValue.x.pinned(between: 0, and: 1) + touchValue.y = 1.0 - touchValue.y.pinned(between: 0, and: 1) + + self.value = touchValue + } + + override public func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + + self.trackIndicator(with: touch) + + growIndicator() + + return true + } + + override public func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + + self.trackIndicator(with: touch) + + return true + } + + override public func endTracking(_ touch: UITouch?, with event: UIEvent?) { + super.endTracking(touch, with: event) + + shrinkIndicator() + } + + override public func cancelTracking(with event: UIEvent?) { + super.cancelTracking(with: event) + + shrinkIndicator() + } + + private func changeIndicatorSize(to size: CGFloat) { + + let center = self.indicator.center + + let size = CGSize(width: size, height: size) + let indicatorRect = CGRect(origin: .zero, size: size) + + self.indicator.frame = indicatorRect + self.indicator.center = center + + } + + private func growIndicator() { + + UIView.animate(withDuration: 0.15, delay: 0.0, options: [.curveEaseIn], animations: { + + self.changeIndicatorSize(to: self.indicatorSizeActive) + + }) { (finished) in + + } + + } + + private func shrinkIndicator() { + + UIView.animate(withDuration: 0.15, delay: 0.0, options: [.curveEaseOut], animations: { + + self.changeIndicatorSize(to: self.indicatorSizeInactive) + self.indicator.setNeedsDisplay() + + }) { (finished) in + + self.indicator.setNeedsDisplay() + + } + + } + +} diff --git a/HueKit/View/ColorSquareView.swift b/HueKit/View/ColorSquareView.swift new file mode 100644 index 0000000..2a97e10 --- /dev/null +++ b/HueKit/View/ColorSquareView.swift @@ -0,0 +1,36 @@ +// +// ColorSquareView.swift +// HueKit +// +// Created by Louis D'hauwe on 25/07/2017. +// Copyright © 2017 Silver Fox. All rights reserved. +// + +import UIKit + +@IBDesignable +class ColorSquareView: UIImageView { + + private var drawnHue: CGFloat = 0.0 + + @IBInspectable + var hue: CGFloat = 0.0 { + didSet { + + if abs(drawnHue - hue) <= 1e-10 { + return + } + + let cgImage = HSBGen.createSaturationBrightnessSquareContentImageWithHue(hue: self.hue * 360.0) + + if let cgImage = cgImage { + self.image = UIImage(cgImage: cgImage) + } else { + assertionFailure("Expected CGImage") + } + + drawnHue = hue + } + } + +} diff --git a/HueKit/View/SourceColorView.swift b/HueKit/View/SourceColorView.swift new file mode 100644 index 0000000..dd21245 --- /dev/null +++ b/HueKit/View/SourceColorView.swift @@ -0,0 +1,89 @@ +// +// SourceColorView.swift +// HueKit +// +// Created by Louis D'hauwe on 30/07/2017. +// Copyright © 2017 Silver Fox. All rights reserved. +// + +import Foundation +import UIKit + +@IBDesignable +public class SourceColorView: UIControl { + + @IBInspectable + public var isTrackingInside: Bool = false { + didSet { + if oldValue != isTrackingInside { + self.setNeedsDisplay() + } + } + } + + @IBInspectable + public var dontShrinkWhenPressed: Bool = false { + didSet { + if oldValue != dontShrinkWhenPressed { + self.setNeedsDisplay() + } + } + } + + public override func draw(_ rect: CGRect) { + super.draw(rect) + + guard isEnabled && isTrackingInside && !dontShrinkWhenPressed else { + return + } + + guard let context = UIGraphicsGetCurrentContext() else { + return + } + + let bounds = self.bounds + + UIColor.white.set() + context.stroke(bounds.insetBy(dx: 1, dy: 1), width: 2) + + UIColor.black.set() + UIRectFrame(bounds.insetBy(dx: 2, dy: 2)) + } + + // MARK: - UIControl overrides + + public override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + + guard self.isEnabled else { + return false + } + + self.isTrackingInside = true + + return super.beginTracking(touch, with: event) + } + + public override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + + let isTrackingInside = self.bounds.contains(touch.location(in: self)) + + self.isTrackingInside = isTrackingInside + + return super.continueTracking(touch, with: event) + } + + public override func endTracking(_ touch: UITouch?, with event: UIEvent?) { + + self.isTrackingInside = false + + super.endTracking(touch, with: event) + } + + public override func cancelTracking(with event: UIEvent?) { + + self.isTrackingInside = false + + super.cancelTracking(with: event) + } + +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d9e9f52 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Louis D'hauwe + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.