├── README.md ├── RRCircularMenu.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── artem.xcuserdatad │ │ ├── UserInterfaceState.xcuserstate │ │ └── xcdebugger │ │ └── Expressions.xcexplist └── xcuserdata │ └── artem.xcuserdatad │ ├── xcdebugger │ └── Breakpoints.xcbkptlist │ └── xcschemes │ ├── RRCircularMenu.xcscheme │ └── xcschememanagement.plist ├── RRCircularMenu ├── AppDelegate.h ├── AppDelegate.m ├── Default-568h@2x.png ├── Default.png ├── Default@2x.png ├── RRCircularMenu-Info.plist ├── RRCircularMenu-Prefix.pch ├── ViewController.h ├── ViewController.m ├── calories.off@2x.png ├── calories@2x.png ├── distance.off@2x.png ├── distance@2x.png ├── en.lproj │ ├── InfoPlist.strings │ └── ViewController.xib ├── hr.off@2x.png ├── hr@2x.png ├── main.m ├── route.off@2x.png ├── route@2x.png ├── speed.off@2x.png ├── speed@2x.png ├── time.off@2x.png └── time@2x.png ├── Sources ├── Helpers.h ├── RRCircularItem.h ├── RRCircularItem.m ├── RRCircularMenu.h ├── RRCircularMenu.m ├── RRCircularMenuLabel.h ├── RRCircularMenuLabel.m ├── RRCircularSlider.h └── RRCircularSlider.m └── screenshot.png /README.md: -------------------------------------------------------------------------------- 1 | RRCircularMenu 2 | ============== 3 | 4 | RRCircularMenu is a Menu, slider and rainbow! All circular, animated and 100% Core Graphics (except for icons). As seen in [Run for iPhone](http://getrunapp.com). 5 | 6 | Here is short demo video: 7 | 8 | [![RRCircularMenu demo](http://img.youtube.com/vi/lrPxjKIlQrg/0.jpg)](http://www.youtube.com/watch?v=lrPxjKIlQrg) 9 | 10 | 11 | 12 | Requirements 13 | --- 14 | 15 | RRCircularMenu has been developed against the iOS 6 SDK, but may run on iOS 5. 16 | 17 | RRCircularMenu *does NOT use ARC*. To use this with ARC, compile with the `-fno-objc-arc` compiler flag. 18 | 19 | Using RRMenuController 20 | --- 21 | 22 | See the demo app for an example. It's pretty simple: 23 | 24 | ``` 25 | // 1. Import the header 26 | #import "RRCircularMenu.h" 27 | 28 | // 2. Instantiate a menu object 29 | menu = [[RRCircularMenu alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height - 180, self.view.frame.size.width, 180)]; 30 | 31 | // 3. Assign a delegate 32 | menu.delegate = self; 33 | 34 | // 4. Install in the view hierarchy 35 | [self.view addSubview:menu]; 36 | 37 | // 5. Display the menu 38 | [menu 39 | showWithAnimationBlock:^{ 40 | self.view.backgroundColor = [UIColor darkGrayColor]; 41 | } 42 | settingSliderTo:3]; 43 | ``` 44 | 45 | The method `showWithAnimationBlock:` is used to reveal menu. The supplied animation block helps if you show the menu modally, so you can do things like prepare a view hierarchy for displaying the menu. 46 | 47 | Items are set up in `RRCircularMenu.m`: 48 | 49 | ``` 50 | - (void) setUpItems { 51 | ... 52 | for (NSString *title in @[@"Time", @"Distance", @"Speed", @"HR", @"Calories", @"Route"]) { 53 | [self addItem:title from:currentAngle to:currentAngle + angleStep index:index++]; 54 | currentAngle += angleStep; 55 | } 56 | ... 57 | } 58 | ``` 59 | 60 | Delegate used for all kinds of events: 61 | 62 | ``` 63 | - (void) menuItem:(RRCircularItem *)item didChangeActive:(BOOL)active; 64 | - (void) menuLabel:(RRCircularMenuLabel *)label didChangeActive:(BOOL)active; 65 | - (BOOL) ignoreClickFor:(RRCircularItem *)item; 66 | - (void) sliderValueChanged:(RRCircularSlider *)slider; 67 | ``` 68 | 69 | Configuring RRMenuController 70 | --- 71 | 72 | This component is NOT really configurable, because it's kind of difficult to configure such menus. But you still can set colors, borders, change number of menu items and stuff like that. If you need whole circle of menu items, you'll have to dig into source code and change animations. 73 | 74 | License 75 | --- 76 | 77 | RRMenuController is Copyright (C) 2013 Artem Salpagarov and released under the MIT License: 78 | 79 | 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: 80 | 81 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 82 | 83 | 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. 84 | 85 | Credits 86 | --- 87 | 88 | Special thanks to [paiv](https://github.com/paiv) for [AngleGradientLayer](https://github.com/paiv/AngleGradientLayer). -------------------------------------------------------------------------------- /RRCircularMenu.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | AC66D74D16E6732400FF9097 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AC66D74C16E6732400FF9097 /* UIKit.framework */; }; 11 | AC66D74F16E6732400FF9097 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AC66D74E16E6732400FF9097 /* Foundation.framework */; }; 12 | AC66D75116E6732400FF9097 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AC66D75016E6732400FF9097 /* CoreGraphics.framework */; }; 13 | AC66D75716E6732400FF9097 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = AC66D75516E6732400FF9097 /* InfoPlist.strings */; }; 14 | AC66D75916E6732400FF9097 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AC66D75816E6732400FF9097 /* main.m */; }; 15 | AC66D75D16E6732400FF9097 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AC66D75C16E6732400FF9097 /* AppDelegate.m */; }; 16 | AC66D75F16E6732500FF9097 /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = AC66D75E16E6732400FF9097 /* Default.png */; }; 17 | AC66D76116E6732500FF9097 /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AC66D76016E6732500FF9097 /* Default@2x.png */; }; 18 | AC66D76316E6732500FF9097 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AC66D76216E6732500FF9097 /* Default-568h@2x.png */; }; 19 | AC66D76616E6732500FF9097 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AC66D76516E6732500FF9097 /* ViewController.m */; }; 20 | AC66D76916E6732500FF9097 /* ViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = AC66D76716E6732500FF9097 /* ViewController.xib */; }; 21 | AC66D77216E6737800FF9097 /* RRCircularMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = AC66D77116E6737800FF9097 /* RRCircularMenu.m */; }; 22 | AC66D77516E6738500FF9097 /* RRCircularItem.m in Sources */ = {isa = PBXBuildFile; fileRef = AC66D77416E6738500FF9097 /* RRCircularItem.m */; }; 23 | AC66D77A16E67FD700FF9097 /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AC66D77916E67FD700FF9097 /* CoreText.framework */; }; 24 | AC66D77C16E7333900FF9097 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AC66D77B16E7333900FF9097 /* QuartzCore.framework */; }; 25 | AC66D77F16E7469500FF9097 /* RRCircularMenuLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = AC66D77E16E7469400FF9097 /* RRCircularMenuLabel.m */; }; 26 | AC66D78316E77AA600FF9097 /* RRCircularSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = AC66D78216E77AA600FF9097 /* RRCircularSlider.m */; }; 27 | AC66D79416E7CCEF00FF9097 /* calories.off@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AC66D78816E7CCEF00FF9097 /* calories.off@2x.png */; }; 28 | AC66D79516E7CCEF00FF9097 /* calories@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AC66D78916E7CCEF00FF9097 /* calories@2x.png */; }; 29 | AC66D79616E7CCEF00FF9097 /* distance.off@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AC66D78A16E7CCEF00FF9097 /* distance.off@2x.png */; }; 30 | AC66D79716E7CCEF00FF9097 /* distance@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AC66D78B16E7CCEF00FF9097 /* distance@2x.png */; }; 31 | AC66D79816E7CCEF00FF9097 /* hr.off@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AC66D78C16E7CCEF00FF9097 /* hr.off@2x.png */; }; 32 | AC66D79916E7CCEF00FF9097 /* hr@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AC66D78D16E7CCEF00FF9097 /* hr@2x.png */; }; 33 | AC66D79A16E7CCEF00FF9097 /* route.off@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AC66D78E16E7CCEF00FF9097 /* route.off@2x.png */; }; 34 | AC66D79B16E7CCEF00FF9097 /* route@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AC66D78F16E7CCEF00FF9097 /* route@2x.png */; }; 35 | AC66D79C16E7CCEF00FF9097 /* speed.off@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AC66D79016E7CCEF00FF9097 /* speed.off@2x.png */; }; 36 | AC66D79D16E7CCEF00FF9097 /* speed@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AC66D79116E7CCEF00FF9097 /* speed@2x.png */; }; 37 | AC66D79E16E7CCEF00FF9097 /* time.off@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AC66D79216E7CCEF00FF9097 /* time.off@2x.png */; }; 38 | AC66D79F16E7CCEF00FF9097 /* time@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AC66D79316E7CCEF00FF9097 /* time@2x.png */; }; 39 | /* End PBXBuildFile section */ 40 | 41 | /* Begin PBXFileReference section */ 42 | AC66D74916E6732400FF9097 /* RRCircularMenu.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RRCircularMenu.app; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | AC66D74C16E6732400FF9097 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 44 | AC66D74E16E6732400FF9097 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 45 | AC66D75016E6732400FF9097 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 46 | AC66D75416E6732400FF9097 /* RRCircularMenu-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "RRCircularMenu-Info.plist"; sourceTree = ""; }; 47 | AC66D75616E6732400FF9097 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 48 | AC66D75816E6732400FF9097 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 49 | AC66D75A16E6732400FF9097 /* RRCircularMenu-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RRCircularMenu-Prefix.pch"; sourceTree = ""; }; 50 | AC66D75B16E6732400FF9097 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 51 | AC66D75C16E6732400FF9097 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 52 | AC66D75E16E6732400FF9097 /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = ""; }; 53 | AC66D76016E6732500FF9097 /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default@2x.png"; sourceTree = ""; }; 54 | AC66D76216E6732500FF9097 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; 55 | AC66D76416E6732500FF9097 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 56 | AC66D76516E6732500FF9097 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 57 | AC66D76816E6732500FF9097 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/ViewController.xib; sourceTree = ""; }; 58 | AC66D77016E6737800FF9097 /* RRCircularMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RRCircularMenu.h; sourceTree = ""; }; 59 | AC66D77116E6737800FF9097 /* RRCircularMenu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RRCircularMenu.m; sourceTree = ""; }; 60 | AC66D77316E6738500FF9097 /* RRCircularItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RRCircularItem.h; sourceTree = ""; }; 61 | AC66D77416E6738500FF9097 /* RRCircularItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RRCircularItem.m; sourceTree = ""; }; 62 | AC66D77916E67FD700FF9097 /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; }; 63 | AC66D77B16E7333900FF9097 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 64 | AC66D77D16E7469400FF9097 /* RRCircularMenuLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RRCircularMenuLabel.h; sourceTree = ""; }; 65 | AC66D77E16E7469400FF9097 /* RRCircularMenuLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RRCircularMenuLabel.m; sourceTree = ""; }; 66 | AC66D78016E74E6F00FF9097 /* Helpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Helpers.h; sourceTree = ""; }; 67 | AC66D78116E77AA600FF9097 /* RRCircularSlider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RRCircularSlider.h; sourceTree = ""; }; 68 | AC66D78216E77AA600FF9097 /* RRCircularSlider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RRCircularSlider.m; sourceTree = ""; }; 69 | AC66D78816E7CCEF00FF9097 /* calories.off@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "calories.off@2x.png"; sourceTree = ""; }; 70 | AC66D78916E7CCEF00FF9097 /* calories@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "calories@2x.png"; sourceTree = ""; }; 71 | AC66D78A16E7CCEF00FF9097 /* distance.off@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "distance.off@2x.png"; sourceTree = ""; }; 72 | AC66D78B16E7CCEF00FF9097 /* distance@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "distance@2x.png"; sourceTree = ""; }; 73 | AC66D78C16E7CCEF00FF9097 /* hr.off@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "hr.off@2x.png"; sourceTree = ""; }; 74 | AC66D78D16E7CCEF00FF9097 /* hr@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "hr@2x.png"; sourceTree = ""; }; 75 | AC66D78E16E7CCEF00FF9097 /* route.off@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "route.off@2x.png"; sourceTree = ""; }; 76 | AC66D78F16E7CCEF00FF9097 /* route@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "route@2x.png"; sourceTree = ""; }; 77 | AC66D79016E7CCEF00FF9097 /* speed.off@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "speed.off@2x.png"; sourceTree = ""; }; 78 | AC66D79116E7CCEF00FF9097 /* speed@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "speed@2x.png"; sourceTree = ""; }; 79 | AC66D79216E7CCEF00FF9097 /* time.off@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "time.off@2x.png"; sourceTree = ""; }; 80 | AC66D79316E7CCEF00FF9097 /* time@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "time@2x.png"; sourceTree = ""; }; 81 | /* End PBXFileReference section */ 82 | 83 | /* Begin PBXFrameworksBuildPhase section */ 84 | AC66D74616E6732400FF9097 /* Frameworks */ = { 85 | isa = PBXFrameworksBuildPhase; 86 | buildActionMask = 2147483647; 87 | files = ( 88 | AC66D77C16E7333900FF9097 /* QuartzCore.framework in Frameworks */, 89 | AC66D77A16E67FD700FF9097 /* CoreText.framework in Frameworks */, 90 | AC66D74D16E6732400FF9097 /* UIKit.framework in Frameworks */, 91 | AC66D74F16E6732400FF9097 /* Foundation.framework in Frameworks */, 92 | AC66D75116E6732400FF9097 /* CoreGraphics.framework in Frameworks */, 93 | ); 94 | runOnlyForDeploymentPostprocessing = 0; 95 | }; 96 | /* End PBXFrameworksBuildPhase section */ 97 | 98 | /* Begin PBXGroup section */ 99 | AC66D74016E6732400FF9097 = { 100 | isa = PBXGroup; 101 | children = ( 102 | AC66D76F16E6735E00FF9097 /* Sources */, 103 | AC66D75216E6732400FF9097 /* RRCircularMenu */, 104 | AC66D74B16E6732400FF9097 /* Frameworks */, 105 | AC66D74A16E6732400FF9097 /* Products */, 106 | ); 107 | sourceTree = ""; 108 | }; 109 | AC66D74A16E6732400FF9097 /* Products */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | AC66D74916E6732400FF9097 /* RRCircularMenu.app */, 113 | ); 114 | name = Products; 115 | sourceTree = ""; 116 | }; 117 | AC66D74B16E6732400FF9097 /* Frameworks */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | AC66D75016E6732400FF9097 /* CoreGraphics.framework */, 121 | AC66D77916E67FD700FF9097 /* CoreText.framework */, 122 | AC66D74E16E6732400FF9097 /* Foundation.framework */, 123 | AC66D77B16E7333900FF9097 /* QuartzCore.framework */, 124 | AC66D74C16E6732400FF9097 /* UIKit.framework */, 125 | ); 126 | name = Frameworks; 127 | sourceTree = ""; 128 | }; 129 | AC66D75216E6732400FF9097 /* RRCircularMenu */ = { 130 | isa = PBXGroup; 131 | children = ( 132 | AC66D75B16E6732400FF9097 /* AppDelegate.h */, 133 | AC66D75C16E6732400FF9097 /* AppDelegate.m */, 134 | AC66D76416E6732500FF9097 /* ViewController.h */, 135 | AC66D76516E6732500FF9097 /* ViewController.m */, 136 | AC66D76716E6732500FF9097 /* ViewController.xib */, 137 | AC66D75316E6732400FF9097 /* Supporting Files */, 138 | ); 139 | path = RRCircularMenu; 140 | sourceTree = ""; 141 | }; 142 | AC66D75316E6732400FF9097 /* Supporting Files */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | AC66D78716E7CCE000FF9097 /* icons */, 146 | AC66D75416E6732400FF9097 /* RRCircularMenu-Info.plist */, 147 | AC66D75516E6732400FF9097 /* InfoPlist.strings */, 148 | AC66D75816E6732400FF9097 /* main.m */, 149 | AC66D75A16E6732400FF9097 /* RRCircularMenu-Prefix.pch */, 150 | AC66D75E16E6732400FF9097 /* Default.png */, 151 | AC66D76016E6732500FF9097 /* Default@2x.png */, 152 | AC66D76216E6732500FF9097 /* Default-568h@2x.png */, 153 | ); 154 | name = "Supporting Files"; 155 | sourceTree = ""; 156 | }; 157 | AC66D76F16E6735E00FF9097 /* Sources */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | AC66D77316E6738500FF9097 /* RRCircularItem.h */, 161 | AC66D77416E6738500FF9097 /* RRCircularItem.m */, 162 | AC66D77016E6737800FF9097 /* RRCircularMenu.h */, 163 | AC66D77116E6737800FF9097 /* RRCircularMenu.m */, 164 | AC66D77D16E7469400FF9097 /* RRCircularMenuLabel.h */, 165 | AC66D77E16E7469400FF9097 /* RRCircularMenuLabel.m */, 166 | AC66D78116E77AA600FF9097 /* RRCircularSlider.h */, 167 | AC66D78216E77AA600FF9097 /* RRCircularSlider.m */, 168 | AC66D78016E74E6F00FF9097 /* Helpers.h */, 169 | ); 170 | path = Sources; 171 | sourceTree = ""; 172 | }; 173 | AC66D78716E7CCE000FF9097 /* icons */ = { 174 | isa = PBXGroup; 175 | children = ( 176 | AC66D78816E7CCEF00FF9097 /* calories.off@2x.png */, 177 | AC66D78916E7CCEF00FF9097 /* calories@2x.png */, 178 | AC66D78A16E7CCEF00FF9097 /* distance.off@2x.png */, 179 | AC66D78B16E7CCEF00FF9097 /* distance@2x.png */, 180 | AC66D78C16E7CCEF00FF9097 /* hr.off@2x.png */, 181 | AC66D78D16E7CCEF00FF9097 /* hr@2x.png */, 182 | AC66D78E16E7CCEF00FF9097 /* route.off@2x.png */, 183 | AC66D78F16E7CCEF00FF9097 /* route@2x.png */, 184 | AC66D79016E7CCEF00FF9097 /* speed.off@2x.png */, 185 | AC66D79116E7CCEF00FF9097 /* speed@2x.png */, 186 | AC66D79216E7CCEF00FF9097 /* time.off@2x.png */, 187 | AC66D79316E7CCEF00FF9097 /* time@2x.png */, 188 | ); 189 | name = icons; 190 | sourceTree = ""; 191 | }; 192 | /* End PBXGroup section */ 193 | 194 | /* Begin PBXNativeTarget section */ 195 | AC66D74816E6732400FF9097 /* RRCircularMenu */ = { 196 | isa = PBXNativeTarget; 197 | buildConfigurationList = AC66D76C16E6732500FF9097 /* Build configuration list for PBXNativeTarget "RRCircularMenu" */; 198 | buildPhases = ( 199 | AC66D74516E6732400FF9097 /* Sources */, 200 | AC66D74616E6732400FF9097 /* Frameworks */, 201 | AC66D74716E6732400FF9097 /* Resources */, 202 | ); 203 | buildRules = ( 204 | ); 205 | dependencies = ( 206 | ); 207 | name = RRCircularMenu; 208 | productName = RRCircularMenu; 209 | productReference = AC66D74916E6732400FF9097 /* RRCircularMenu.app */; 210 | productType = "com.apple.product-type.application"; 211 | }; 212 | /* End PBXNativeTarget section */ 213 | 214 | /* Begin PBXProject section */ 215 | AC66D74116E6732400FF9097 /* Project object */ = { 216 | isa = PBXProject; 217 | attributes = { 218 | LastUpgradeCheck = 0460; 219 | ORGANIZATIONNAME = "Artem Salpagarov"; 220 | }; 221 | buildConfigurationList = AC66D74416E6732400FF9097 /* Build configuration list for PBXProject "RRCircularMenu" */; 222 | compatibilityVersion = "Xcode 3.2"; 223 | developmentRegion = English; 224 | hasScannedForEncodings = 0; 225 | knownRegions = ( 226 | en, 227 | ); 228 | mainGroup = AC66D74016E6732400FF9097; 229 | productRefGroup = AC66D74A16E6732400FF9097 /* Products */; 230 | projectDirPath = ""; 231 | projectRoot = ""; 232 | targets = ( 233 | AC66D74816E6732400FF9097 /* RRCircularMenu */, 234 | ); 235 | }; 236 | /* End PBXProject section */ 237 | 238 | /* Begin PBXResourcesBuildPhase section */ 239 | AC66D74716E6732400FF9097 /* Resources */ = { 240 | isa = PBXResourcesBuildPhase; 241 | buildActionMask = 2147483647; 242 | files = ( 243 | AC66D75716E6732400FF9097 /* InfoPlist.strings in Resources */, 244 | AC66D75F16E6732500FF9097 /* Default.png in Resources */, 245 | AC66D76116E6732500FF9097 /* Default@2x.png in Resources */, 246 | AC66D76316E6732500FF9097 /* Default-568h@2x.png in Resources */, 247 | AC66D76916E6732500FF9097 /* ViewController.xib in Resources */, 248 | AC66D79416E7CCEF00FF9097 /* calories.off@2x.png in Resources */, 249 | AC66D79516E7CCEF00FF9097 /* calories@2x.png in Resources */, 250 | AC66D79616E7CCEF00FF9097 /* distance.off@2x.png in Resources */, 251 | AC66D79716E7CCEF00FF9097 /* distance@2x.png in Resources */, 252 | AC66D79816E7CCEF00FF9097 /* hr.off@2x.png in Resources */, 253 | AC66D79916E7CCEF00FF9097 /* hr@2x.png in Resources */, 254 | AC66D79A16E7CCEF00FF9097 /* route.off@2x.png in Resources */, 255 | AC66D79B16E7CCEF00FF9097 /* route@2x.png in Resources */, 256 | AC66D79C16E7CCEF00FF9097 /* speed.off@2x.png in Resources */, 257 | AC66D79D16E7CCEF00FF9097 /* speed@2x.png in Resources */, 258 | AC66D79E16E7CCEF00FF9097 /* time.off@2x.png in Resources */, 259 | AC66D79F16E7CCEF00FF9097 /* time@2x.png in Resources */, 260 | ); 261 | runOnlyForDeploymentPostprocessing = 0; 262 | }; 263 | /* End PBXResourcesBuildPhase section */ 264 | 265 | /* Begin PBXSourcesBuildPhase section */ 266 | AC66D74516E6732400FF9097 /* Sources */ = { 267 | isa = PBXSourcesBuildPhase; 268 | buildActionMask = 2147483647; 269 | files = ( 270 | AC66D75916E6732400FF9097 /* main.m in Sources */, 271 | AC66D75D16E6732400FF9097 /* AppDelegate.m in Sources */, 272 | AC66D76616E6732500FF9097 /* ViewController.m in Sources */, 273 | AC66D77216E6737800FF9097 /* RRCircularMenu.m in Sources */, 274 | AC66D77516E6738500FF9097 /* RRCircularItem.m in Sources */, 275 | AC66D77F16E7469500FF9097 /* RRCircularMenuLabel.m in Sources */, 276 | AC66D78316E77AA600FF9097 /* RRCircularSlider.m in Sources */, 277 | ); 278 | runOnlyForDeploymentPostprocessing = 0; 279 | }; 280 | /* End PBXSourcesBuildPhase section */ 281 | 282 | /* Begin PBXVariantGroup section */ 283 | AC66D75516E6732400FF9097 /* InfoPlist.strings */ = { 284 | isa = PBXVariantGroup; 285 | children = ( 286 | AC66D75616E6732400FF9097 /* en */, 287 | ); 288 | name = InfoPlist.strings; 289 | sourceTree = ""; 290 | }; 291 | AC66D76716E6732500FF9097 /* ViewController.xib */ = { 292 | isa = PBXVariantGroup; 293 | children = ( 294 | AC66D76816E6732500FF9097 /* en */, 295 | ); 296 | name = ViewController.xib; 297 | sourceTree = ""; 298 | }; 299 | /* End PBXVariantGroup section */ 300 | 301 | /* Begin XCBuildConfiguration section */ 302 | AC66D76A16E6732500FF9097 /* Debug */ = { 303 | isa = XCBuildConfiguration; 304 | buildSettings = { 305 | ALWAYS_SEARCH_USER_PATHS = NO; 306 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 307 | CLANG_CXX_LIBRARY = "libc++"; 308 | CLANG_WARN_CONSTANT_CONVERSION = YES; 309 | CLANG_WARN_EMPTY_BODY = YES; 310 | CLANG_WARN_ENUM_CONVERSION = YES; 311 | CLANG_WARN_INT_CONVERSION = YES; 312 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 313 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 314 | COPY_PHASE_STRIP = NO; 315 | GCC_C_LANGUAGE_STANDARD = gnu99; 316 | GCC_DYNAMIC_NO_PIC = NO; 317 | GCC_OPTIMIZATION_LEVEL = 0; 318 | GCC_PREPROCESSOR_DEFINITIONS = ( 319 | "DEBUG=1", 320 | "$(inherited)", 321 | ); 322 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 323 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 324 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 325 | GCC_WARN_UNUSED_VARIABLE = YES; 326 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 327 | ONLY_ACTIVE_ARCH = YES; 328 | SDKROOT = iphoneos; 329 | }; 330 | name = Debug; 331 | }; 332 | AC66D76B16E6732500FF9097 /* Release */ = { 333 | isa = XCBuildConfiguration; 334 | buildSettings = { 335 | ALWAYS_SEARCH_USER_PATHS = NO; 336 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 337 | CLANG_CXX_LIBRARY = "libc++"; 338 | CLANG_WARN_CONSTANT_CONVERSION = YES; 339 | CLANG_WARN_EMPTY_BODY = YES; 340 | CLANG_WARN_ENUM_CONVERSION = YES; 341 | CLANG_WARN_INT_CONVERSION = YES; 342 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 343 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 344 | COPY_PHASE_STRIP = YES; 345 | GCC_C_LANGUAGE_STANDARD = gnu99; 346 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 347 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 348 | GCC_WARN_UNUSED_VARIABLE = YES; 349 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 350 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 351 | SDKROOT = iphoneos; 352 | VALIDATE_PRODUCT = YES; 353 | }; 354 | name = Release; 355 | }; 356 | AC66D76D16E6732500FF9097 /* Debug */ = { 357 | isa = XCBuildConfiguration; 358 | buildSettings = { 359 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 360 | GCC_PREFIX_HEADER = "RRCircularMenu/RRCircularMenu-Prefix.pch"; 361 | INFOPLIST_FILE = "RRCircularMenu/RRCircularMenu-Info.plist"; 362 | PRODUCT_NAME = "$(TARGET_NAME)"; 363 | WRAPPER_EXTENSION = app; 364 | }; 365 | name = Debug; 366 | }; 367 | AC66D76E16E6732500FF9097 /* Release */ = { 368 | isa = XCBuildConfiguration; 369 | buildSettings = { 370 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 371 | GCC_PREFIX_HEADER = "RRCircularMenu/RRCircularMenu-Prefix.pch"; 372 | INFOPLIST_FILE = "RRCircularMenu/RRCircularMenu-Info.plist"; 373 | PRODUCT_NAME = "$(TARGET_NAME)"; 374 | WRAPPER_EXTENSION = app; 375 | }; 376 | name = Release; 377 | }; 378 | /* End XCBuildConfiguration section */ 379 | 380 | /* Begin XCConfigurationList section */ 381 | AC66D74416E6732400FF9097 /* Build configuration list for PBXProject "RRCircularMenu" */ = { 382 | isa = XCConfigurationList; 383 | buildConfigurations = ( 384 | AC66D76A16E6732500FF9097 /* Debug */, 385 | AC66D76B16E6732500FF9097 /* Release */, 386 | ); 387 | defaultConfigurationIsVisible = 0; 388 | defaultConfigurationName = Release; 389 | }; 390 | AC66D76C16E6732500FF9097 /* Build configuration list for PBXNativeTarget "RRCircularMenu" */ = { 391 | isa = XCConfigurationList; 392 | buildConfigurations = ( 393 | AC66D76D16E6732500FF9097 /* Debug */, 394 | AC66D76E16E6732500FF9097 /* Release */, 395 | ); 396 | defaultConfigurationIsVisible = 0; 397 | defaultConfigurationName = Release; 398 | }; 399 | /* End XCConfigurationList section */ 400 | }; 401 | rootObject = AC66D74116E6732400FF9097 /* Project object */; 402 | } 403 | -------------------------------------------------------------------------------- /RRCircularMenu.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /RRCircularMenu.xcodeproj/project.xcworkspace/xcuserdata/artem.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iartem/RRCircularMenu/e320a67774b3ba04638a6fcec1230ff8738715d3/RRCircularMenu.xcodeproj/project.xcworkspace/xcuserdata/artem.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /RRCircularMenu.xcodeproj/project.xcworkspace/xcuserdata/artem.xcuserdatad/xcdebugger/Expressions.xcexplist: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /RRCircularMenu.xcodeproj/xcuserdata/artem.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /RRCircularMenu.xcodeproj/xcuserdata/artem.xcuserdatad/xcschemes/RRCircularMenu.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /RRCircularMenu.xcodeproj/xcuserdata/artem.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | RRCircularMenu.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | AC66D74816E6732400FF9097 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /RRCircularMenu/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // RRCircularMenu 4 | // 5 | // Copyright (C) 2013 Artem Salpagarov 6 | 7 | // 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: 8 | 9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | // 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. 12 | 13 | #import 14 | 15 | @class ViewController; 16 | 17 | @interface AppDelegate : UIResponder 18 | 19 | @property (strong, nonatomic) UIWindow *window; 20 | 21 | @property (strong, nonatomic) ViewController *viewController; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /RRCircularMenu/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // RRCircularMenu 4 | // 5 | // Copyright (C) 2013 Artem Salpagarov 6 | 7 | // 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: 8 | 9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | // 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. 12 | 13 | #import "AppDelegate.h" 14 | 15 | #import "ViewController.h" 16 | 17 | @implementation AppDelegate 18 | 19 | - (void)dealloc 20 | { 21 | [_window release]; 22 | [_viewController release]; 23 | [super dealloc]; 24 | } 25 | 26 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 27 | { 28 | self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease]; 29 | // Override point for customization after application launch. 30 | self.viewController = [[[ViewController alloc] initWithNibName:@"ViewController" bundle:nil] autorelease]; 31 | self.window.rootViewController = self.viewController; 32 | [self.window makeKeyAndVisible]; 33 | return YES; 34 | } 35 | 36 | - (void)applicationWillResignActive:(UIApplication *)application 37 | { 38 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 39 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 40 | } 41 | 42 | - (void)applicationDidEnterBackground:(UIApplication *)application 43 | { 44 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 45 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 46 | } 47 | 48 | - (void)applicationWillEnterForeground:(UIApplication *)application 49 | { 50 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 51 | } 52 | 53 | - (void)applicationDidBecomeActive:(UIApplication *)application 54 | { 55 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 56 | } 57 | 58 | - (void)applicationWillTerminate:(UIApplication *)application 59 | { 60 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 61 | } 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /RRCircularMenu/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iartem/RRCircularMenu/e320a67774b3ba04638a6fcec1230ff8738715d3/RRCircularMenu/Default-568h@2x.png -------------------------------------------------------------------------------- /RRCircularMenu/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iartem/RRCircularMenu/e320a67774b3ba04638a6fcec1230ff8738715d3/RRCircularMenu/Default.png -------------------------------------------------------------------------------- /RRCircularMenu/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iartem/RRCircularMenu/e320a67774b3ba04638a6fcec1230ff8738715d3/RRCircularMenu/Default@2x.png -------------------------------------------------------------------------------- /RRCircularMenu/RRCircularMenu-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | com.getrunapp.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /RRCircularMenu/RRCircularMenu-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'RRCircularMenu' target in the 'RRCircularMenu' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_4_0 8 | #warning "This project uses features only available in iOS SDK 4.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #endif 15 | -------------------------------------------------------------------------------- /RRCircularMenu/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // RRCircularMenu 4 | // 5 | // Copyright (C) 2013 Artem Salpagarov 6 | 7 | // 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: 8 | 9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | // 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. 12 | 13 | #import 14 | #import "RRCircularMenu.h" 15 | 16 | @interface ViewController : UIViewController 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /RRCircularMenu/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // RRCircularMenu 4 | // 5 | // Copyright (C) 2013 Artem Salpagarov 6 | 7 | // 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: 8 | 9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | // 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. 12 | 13 | #import "ViewController.h" 14 | 15 | @interface ViewController () 16 | 17 | @end 18 | 19 | @implementation ViewController { 20 | RRCircularMenu *menu; 21 | } 22 | 23 | - (void)viewWillAppear:(BOOL)animated { 24 | [super viewWillAppear:animated]; 25 | self.view.backgroundColor = [UIColor whiteColor]; 26 | 27 | UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; 28 | [button setTitle:@"Open menu" forState:UIControlStateNormal]; 29 | [button setFrame:CGRectMake(100, self.view.frame.size.height - 45, 120, 40)]; 30 | [button addTarget:self action:@selector(openMenu) forControlEvents:UIControlEventTouchUpInside]; 31 | [self.view addSubview:button]; 32 | 33 | UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(openMenu)]; 34 | [self.view addGestureRecognizer:tap]; 35 | [tap release]; 36 | } 37 | 38 | - (void) openMenu { 39 | if (menu) { 40 | [menu hideWithAnimationBlock:^{ 41 | self.view.backgroundColor = [UIColor whiteColor]; 42 | }]; 43 | [menu release], menu = nil; 44 | } else { 45 | menu = [[RRCircularMenu alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height - 180, self.view.frame.size.width, 180)]; 46 | menu.delegate = self; 47 | 48 | [self.view addSubview:menu]; 49 | [menu showWithAnimationBlock:^{ 50 | self.view.backgroundColor = [UIColor darkGrayColor]; 51 | } settingSliderTo:3]; 52 | } 53 | } 54 | 55 | # pragma mark - Menu Delegate methods 56 | 57 | - (void) menuItem:(RRCircularItem *)item didChangeActive:(BOOL)active { 58 | NSLog(@"Item %@ did change state to %d", item.text, active); 59 | if (active && ![menu isLabelActive]) { 60 | [menu setLabelActive:YES]; 61 | [menu setSliderValue:1]; 62 | } else if (!active && [menu isLabelActive]) { 63 | BOOL hasActive = NO; 64 | for (int i = 0; i < 6; i++) hasActive |= [menu isItemActive:i]; 65 | if (!hasActive) { 66 | [menu setLabelActive:NO]; 67 | [menu setSliderValue:0 animated:NO]; 68 | } 69 | } 70 | } 71 | - (void) menuLabel:(RRCircularMenuLabel *)label didChangeActive:(BOOL)active { 72 | NSLog(@"Label did change state to %d (%@)", active, label.text); 73 | if (active && [menu sliderValue] == 0) { 74 | [menu setSliderValue:1]; 75 | [menu setItem:0 active:YES]; 76 | } else if (!active && [menu sliderValue] != 0) { 77 | [menu setSliderValue:0 animated:NO]; 78 | for (int i = 0; i < 6; i++) [menu setItem:i active:NO]; 79 | } 80 | } 81 | 82 | - (BOOL) ignoreClickFor:(RRCircularItem *)item { 83 | NSLog(@"Checking whether to ignore click for item %@", item.text); 84 | return NO; 85 | } 86 | 87 | - (void) sliderValueChanged:(RRCircularSlider *)slider { 88 | NSLog(@"Slider value changed to %d", slider.value); 89 | if (slider.value == 0) { 90 | [menu setLabelActive:NO]; 91 | [menu setLabelText:@"CUES\nOFF"]; 92 | for (int i = 0; i < 6; i++) [menu setItem:i active:NO]; 93 | } else { 94 | [menu setLabelActive:YES]; 95 | 96 | if (slider.value == 1) { 97 | [menu setLabelText:@"AUTO-\nMAGICAL"]; 98 | } else if (slider.value == 2) { 99 | [menu setLabelText:@"EVERY\n5 min"]; 100 | } else if (slider.value == 3) { 101 | [menu setLabelText:@"EVERY\n10 min"]; 102 | } else if (slider.value == 4) { 103 | [menu setLabelText:@"EVERY\n1 km"]; 104 | } else if (slider.value == 5) { 105 | [menu setLabelText:@"EVERY\n3 km"]; 106 | } 107 | } 108 | } 109 | 110 | 111 | @end 112 | -------------------------------------------------------------------------------- /RRCircularMenu/calories.off@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iartem/RRCircularMenu/e320a67774b3ba04638a6fcec1230ff8738715d3/RRCircularMenu/calories.off@2x.png -------------------------------------------------------------------------------- /RRCircularMenu/calories@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iartem/RRCircularMenu/e320a67774b3ba04638a6fcec1230ff8738715d3/RRCircularMenu/calories@2x.png -------------------------------------------------------------------------------- /RRCircularMenu/distance.off@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iartem/RRCircularMenu/e320a67774b3ba04638a6fcec1230ff8738715d3/RRCircularMenu/distance.off@2x.png -------------------------------------------------------------------------------- /RRCircularMenu/distance@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iartem/RRCircularMenu/e320a67774b3ba04638a6fcec1230ff8738715d3/RRCircularMenu/distance@2x.png -------------------------------------------------------------------------------- /RRCircularMenu/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /RRCircularMenu/en.lproj/ViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1536 5 | 12A269 6 | 2835 7 | 1187 8 | 624.00 9 | 10 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 11 | 1919 12 | 13 | 14 | IBProxyObject 15 | IBUIView 16 | 17 | 18 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 19 | 20 | 21 | PluginDependencyRecalculationVersion 22 | 23 | 24 | 25 | 26 | IBFilesOwner 27 | IBCocoaTouchFramework 28 | 29 | 30 | IBFirstResponder 31 | IBCocoaTouchFramework 32 | 33 | 34 | 35 | 274 36 | {{0, 20}, {320, 548}} 37 | 38 | 39 | 40 | 41 | 3 42 | MC43NQA 43 | 44 | 2 45 | 46 | 47 | NO 48 | 49 | 50 | IBUIScreenMetrics 51 | 52 | YES 53 | 54 | 55 | 56 | 57 | 58 | {320, 568} 59 | {568, 320} 60 | 61 | 62 | IBCocoaTouchFramework 63 | Retina 4 Full Screen 64 | 2 65 | 66 | IBCocoaTouchFramework 67 | 68 | 69 | 70 | 71 | 72 | 73 | view 74 | 75 | 76 | 77 | 7 78 | 79 | 80 | 81 | 82 | 83 | 0 84 | 85 | 86 | 87 | 88 | 89 | -1 90 | 91 | 92 | File's Owner 93 | 94 | 95 | -2 96 | 97 | 98 | 99 | 100 | 6 101 | 102 | 103 | 104 | 105 | 106 | 107 | ViewController 108 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 109 | UIResponder 110 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 111 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 112 | 113 | 114 | 115 | 116 | 117 | 7 118 | 119 | 120 | 121 | 122 | ViewController 123 | UIViewController 124 | 125 | IBProjectSource 126 | ./Classes/ViewController.h 127 | 128 | 129 | 130 | 131 | 0 132 | IBCocoaTouchFramework 133 | YES 134 | 3 135 | YES 136 | 1919 137 | 138 | 139 | -------------------------------------------------------------------------------- /RRCircularMenu/hr.off@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iartem/RRCircularMenu/e320a67774b3ba04638a6fcec1230ff8738715d3/RRCircularMenu/hr.off@2x.png -------------------------------------------------------------------------------- /RRCircularMenu/hr@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iartem/RRCircularMenu/e320a67774b3ba04638a6fcec1230ff8738715d3/RRCircularMenu/hr@2x.png -------------------------------------------------------------------------------- /RRCircularMenu/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // RRCircularMenu 4 | // 5 | // Copyright (C) 2013 Artem Salpagarov 6 | 7 | // 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: 8 | 9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | // 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. 12 | 13 | #import 14 | 15 | #import "AppDelegate.h" 16 | 17 | int main(int argc, char *argv[]) 18 | { 19 | @autoreleasepool { 20 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /RRCircularMenu/route.off@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iartem/RRCircularMenu/e320a67774b3ba04638a6fcec1230ff8738715d3/RRCircularMenu/route.off@2x.png -------------------------------------------------------------------------------- /RRCircularMenu/route@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iartem/RRCircularMenu/e320a67774b3ba04638a6fcec1230ff8738715d3/RRCircularMenu/route@2x.png -------------------------------------------------------------------------------- /RRCircularMenu/speed.off@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iartem/RRCircularMenu/e320a67774b3ba04638a6fcec1230ff8738715d3/RRCircularMenu/speed.off@2x.png -------------------------------------------------------------------------------- /RRCircularMenu/speed@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iartem/RRCircularMenu/e320a67774b3ba04638a6fcec1230ff8738715d3/RRCircularMenu/speed@2x.png -------------------------------------------------------------------------------- /RRCircularMenu/time.off@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iartem/RRCircularMenu/e320a67774b3ba04638a6fcec1230ff8738715d3/RRCircularMenu/time.off@2x.png -------------------------------------------------------------------------------- /RRCircularMenu/time@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iartem/RRCircularMenu/e320a67774b3ba04638a6fcec1230ff8738715d3/RRCircularMenu/time@2x.png -------------------------------------------------------------------------------- /Sources/Helpers.h: -------------------------------------------------------------------------------- 1 | // 2 | // Helpers.h 3 | // RRCircularMenu 4 | // 5 | // Copyright (C) 2013 Artem Salpagarov 6 | 7 | // 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: 8 | 9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | // 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. 12 | 13 | #import 14 | #import 15 | 16 | static inline id _release(id obj) { 17 | if (obj) [obj release], obj = nil; 18 | return obj; 19 | } 20 | 21 | static inline CGRect CGRectAdd(CGRect r, CGFloat x, CGFloat y, CGFloat width, CGFloat height) { 22 | CGRect rect; 23 | rect.origin.x = r.origin.x + x; 24 | rect.origin.y = r.origin.y + y; 25 | rect.size.width = r.size.width + width; 26 | rect.size.height = r.size.height + height; 27 | return rect; 28 | } 29 | 30 | static CGAffineTransform CGAffineTransformMakeRotationAt(CGFloat angle, CGPoint pt){ 31 | const CGFloat fx = pt.x; 32 | const CGFloat fy = pt.y; 33 | const CGFloat fcos = cos(angle); 34 | const CGFloat fsin = sin(angle); 35 | return CGAffineTransformMake(fcos, fsin, -fsin, fcos, fx - fx * fcos + fy * fsin, fy - fx * fsin - fy * fcos); 36 | } 37 | 38 | static void RRViewSetAnchorPoint(UIView *view, CGPoint anchorPoint) { 39 | CGPoint newPoint = CGPointMake(view.bounds.size.width * anchorPoint.x, view.bounds.size.height * anchorPoint.y); 40 | CGPoint oldPoint = CGPointMake(view.bounds.size.width * view.layer.anchorPoint.x, view.bounds.size.height * view.layer.anchorPoint.y); 41 | 42 | newPoint = CGPointApplyAffineTransform(newPoint, view.transform); 43 | oldPoint = CGPointApplyAffineTransform(oldPoint, view.transform); 44 | 45 | CGPoint position = view.layer.position; 46 | 47 | position.x -= oldPoint.x; 48 | position.x += newPoint.x; 49 | 50 | position.y -= oldPoint.y; 51 | position.y += newPoint.y; 52 | 53 | view.layer.position = position; 54 | view.layer.anchorPoint = anchorPoint; 55 | } 56 | -------------------------------------------------------------------------------- /Sources/RRCircularItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // RRCircularItem.h 3 | // RRCircularMenu 4 | // 5 | // Copyright (C) 2013 Artem Salpagarov 6 | 7 | // 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: 8 | 9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | // 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. 12 | 13 | #import 14 | 15 | @interface RRCircularItem : UIControl 16 | 17 | // Whether item is active (activeTextColor of text, active icon, activeColor background) or not 18 | @property (nonatomic) BOOL active; 19 | 20 | // Background color of circle segment (when selected / touched or not) 21 | @property (nonatomic, retain) UIColor *activeColor, *inactiveColor; 22 | 23 | // Active / inactive color of title (when selected / touched or not) 24 | @property (nonatomic, retain) UIColor *activeTextColor, *inactiveTextColor; 25 | 26 | // Color & width of right border 27 | @property (nonatomic, retain) UIColor *borderColor; 28 | @property (nonatomic) float borderWidth; 29 | 30 | // Title of segment 31 | @property (nonatomic, retain) NSString *text; 32 | @property (nonatomic, retain) UIFont *font; 33 | 34 | // Optional icons 35 | @property (nonatomic, retain) UIImage *activeImage, *inactiveImage; 36 | 37 | // Radius of arc in pixels 38 | @property (nonatomic) float radius; 39 | // Radius of title arc in pixels 40 | @property (nonatomic) float textRadius; 41 | // Center of circle in pixels from self.frame.origin 42 | @property (nonatomic) CGPoint center; 43 | // Size for icons 44 | @property (nonatomic) CGSize iconSize; 45 | @property (nonatomic) float iconRadius; 46 | 47 | // Angles in degrees 48 | @property (nonatomic) float angleFrom, angleTo; 49 | 50 | 51 | // Fixes for first and last item to fill all screen space 52 | @property (nonatomic) BOOL firstOne, lastOne; 53 | 54 | - (BOOL) hitInside:(CGPoint)point; 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /Sources/RRCircularItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // RRCircularItem.m 3 | // RRCircularMenu 4 | // 5 | // Copyright (C) 2013 Artem Salpagarov 6 | 7 | // 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: 8 | 9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | // 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. 12 | 13 | #import "RRCircularItem.h" 14 | #import 15 | #import 16 | #import "Helpers.h" 17 | 18 | @implementation RRCircularItem 19 | 20 | @synthesize active = _active; 21 | @synthesize activeColor = _activeColor, inactiveColor = _inactiveColor, activeTextColor = _activeTextColor, inactiveTextColor = _inactiveTextColor, borderColor = _borderColor; 22 | @synthesize borderWidth = _borderWidth, radius = _radius, textRadius = _textRadius, center = _center, angleFrom = _angleFrom, angleTo = _angleTo, iconSize = _iconSize, iconRadius = _iconRadius; 23 | 24 | @synthesize text = _text, font = _font, activeImage = _activeImage, inactiveImage = _inactiveImage; 25 | 26 | @synthesize firstOne = _firstOne, lastOne = _lastOne; 27 | 28 | - (id)initWithFrame:(CGRect)frame { 29 | self = [super initWithFrame:frame]; 30 | if (self) { 31 | self.backgroundColor = [UIColor clearColor]; 32 | self.activeColor = [UIColor colorWithRed:242.0/255 green:242.0/255 blue:242.0/255 alpha:1]; // #F2F2F2 33 | self.inactiveColor = [UIColor colorWithRed:242.0/255 green:242.0/255 blue:242.0/255 alpha:1]; // #DBDBDB 34 | self.borderColor = [UIColor colorWithRed:219.0/255 green:219.0/255 blue:219.0/255 alpha:1]; // #DBDBDB 35 | self.inactiveTextColor = [UIColor colorWithRed:219.0/255 green:219.0/255 blue:219.0/255 alpha:1]; // #DBDBDB 36 | self.activeTextColor = [UIColor colorWithRed:51.0/255 green:51.0/255 blue:51.0/255 alpha:1]; // #333333 37 | self.borderWidth = 2; 38 | self.radius = 300; 39 | self.angleFrom = -100; 40 | self.angleTo = 100; 41 | self.font = [UIFont systemFontOfSize:14]; 42 | } 43 | return self; 44 | } 45 | 46 | - (void) dealloc { 47 | _activeColor = _release(_activeColor); 48 | _inactiveColor = _release(_inactiveColor); 49 | _borderColor = _release(_borderColor); 50 | _inactiveTextColor = _release(_inactiveTextColor); 51 | _activeTextColor = _release(_activeTextColor); 52 | _font = _release(_font); 53 | [super dealloc]; 54 | } 55 | 56 | # pragma mark - Getters and Setters 57 | 58 | - (void) setActive:(BOOL)active { 59 | _active = active; 60 | [self sendActionsForControlEvents:UIControlEventValueChanged]; 61 | } 62 | 63 | - (void) setAngleTo:(float)angleTo { 64 | _angleTo = M_PI * angleTo / 180; 65 | } 66 | 67 | - (void) setAngleFrom:(float)angleFrom { 68 | _angleFrom = M_PI * angleFrom / 180; 69 | } 70 | 71 | # pragma mark - Drawing 72 | 73 | - (void)drawRect:(CGRect)rect { 74 | UIColor *background = _active ? _activeColor : _inactiveColor; 75 | UIImage *icon = _active ? _activeImage : _inactiveImage; 76 | UIColor *title = _active ? _activeTextColor : _inactiveTextColor; 77 | 78 | CGRect f = self.bounds; 79 | CGContextRef ctx = UIGraphicsGetCurrentContext(); 80 | CGContextSaveGState(ctx); 81 | 82 | // Center of the circle 83 | CGContextTranslateCTM(ctx, f.origin.x + _center.x, f.origin.y + _center.y); 84 | 85 | // First arc point 86 | CGPoint from = CGPointMake(cosf(_angleFrom) * _radius, sinf(_angleFrom) * _radius); 87 | // Last arc point 88 | CGPoint to = CGPointMake(cosf(_angleTo) * _radius, sinf(_angleTo) * _radius); 89 | 90 | // Draw arc 91 | CGContextMoveToPoint(ctx, 0, 0); 92 | CGContextAddLineToPoint(ctx, from.x, from.y); 93 | CGContextAddArc(ctx, 0, 0, _radius, _angleFrom, _angleTo, NO); 94 | CGContextAddLineToPoint(ctx, 0, 0); 95 | 96 | // Fill arc 97 | [background setFill]; 98 | CGContextFillPath(ctx); 99 | 100 | // Fixes for first segment and last segment to fill all screen space 101 | if (_firstOne) { 102 | CGContextAddRect(ctx, CGRectMake(from.x, from.y - (self.frame.size.height - _center.y), _radius, self.frame.size.height - from.y)); 103 | CGContextFillPath(ctx); 104 | } else if (_lastOne) { 105 | CGContextAddRect(ctx, CGRectMake(to.x - _radius, to.y - (self.frame.size.height - _center.y), _radius, self.frame.size.height - from.y)); 106 | CGContextFillPath(ctx); 107 | } 108 | 109 | // Draw border 110 | CGContextMoveToPoint(ctx, to.x, to.y); 111 | CGContextAddLineToPoint(ctx, 0, 0); 112 | CGContextSetLineWidth(ctx, _borderWidth); 113 | [_borderColor setStroke]; 114 | CGContextStrokePath(ctx); 115 | 116 | // Draw title 117 | CGSize size = [_text sizeWithFont:_font]; 118 | float arc = _textRadius * fabs(_angleTo - _angleFrom); 119 | float offset = (arc - size.width) / 2; 120 | 121 | CGContextRotateCTM(ctx, M_PI_2 + (_angleTo + _angleFrom) / 2); 122 | [title setFill]; 123 | [_text drawInRect:CGRectMake(-arc / 2 + offset, -_textRadius - size.height / 2, size.width, size.height) withFont:_font]; 124 | CGContextFillPath(ctx); 125 | 126 | // Draw icon 127 | // Try to fit icon in segment at iconRadius 128 | // You should NSLog width and height and use icons of calculated size 129 | float distance = sqrtf(powf(to.x - from.x, 2) + powf(to.y - from.y, 2)); 130 | float width = floor(distance * _iconRadius / _radius); 131 | float height = floor(width * icon.size.height / icon.size.width); 132 | [icon drawInRect:CGRectMake(-width / 2, -_iconRadius - height, width, height)]; 133 | // NSLog(@"width %f, height %f", width, height); 134 | 135 | CGContextRestoreGState(ctx); 136 | 137 | } 138 | 139 | # pragma mark - Events 140 | 141 | // Checks whether point is inside circle segment or not 142 | - (BOOL) hitInside:(CGPoint)point { 143 | float x = point.x - self.bounds.origin.x - _center.x; 144 | float y = -point.y + self.bounds.origin.y + _center.y; 145 | float hypot = hypotf(x, y); 146 | if (hypot > _radius) return NO; 147 | 148 | float angle; 149 | if (x / hypot > 0) { 150 | angle = - asinf(y / hypot); 151 | } else if (x / hypot < 0) { 152 | angle = asinf(y / hypot) - M_PI; 153 | } else { 154 | angle = - M_PI_2; 155 | } 156 | 157 | return angle > _angleFrom && angle < _angleTo; 158 | } 159 | 160 | @end 161 | -------------------------------------------------------------------------------- /Sources/RRCircularMenu.h: -------------------------------------------------------------------------------- 1 | // 2 | // RRCircularMenu.h 3 | // RRCircularMenu 4 | // 5 | // Copyright (C) 2013 Artem Salpagarov 6 | 7 | // 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: 8 | 9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | // 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. 12 | 13 | #import 14 | 15 | #import "RRCircularItem.h" 16 | #import "RRCircularMenu.h" 17 | #import "RRCircularMenuLabel.h" 18 | #import "RRCircularSlider.h" 19 | 20 | @protocol RRCircularMenuDelegate 21 | @optional 22 | - (void) menuItem:(RRCircularItem *)item didChangeActive:(BOOL)active; 23 | - (void) menuLabel:(RRCircularMenuLabel *)label didChangeActive:(BOOL)active; 24 | 25 | - (BOOL) ignoreClickFor:(RRCircularItem *)item; 26 | 27 | - (void) sliderValueChanged:(RRCircularSlider *)slider; 28 | 29 | @end 30 | 31 | @interface RRCircularMenu : UIView 32 | 33 | @property (nonatomic, assign) id delegate; 34 | 35 | - (void) setItem:(int)index active:(BOOL)active; 36 | - (BOOL) isItemActive:(int)index; 37 | 38 | - (void) setLabelActive:(BOOL)active; 39 | - (BOOL) isLabelActive; 40 | - (void) setLabelText:(NSString *)text; 41 | 42 | - (void) setSliderValue:(int)value; 43 | - (void) setSliderValue:(int)value animated:(BOOL)animated; 44 | - (int) sliderValue; 45 | 46 | - (void) showWithAnimationBlock:(void(^)(void))block settingSliderTo:(int)value; 47 | - (void) hideWithAnimationBlock:(void(^)(void))block; 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /Sources/RRCircularMenu.m: -------------------------------------------------------------------------------- 1 | // 2 | // RRCircularMenu.m 3 | // RRCircularMenu 4 | // 5 | // Copyright (C) 2013 Artem Salpagarov 6 | 7 | // 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: 8 | 9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | // 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. 12 | 13 | #import "RRCircularMenu.h" 14 | #import "Helpers.h" 15 | #import 16 | 17 | @implementation RRCircularMenu { 18 | NSMutableArray *items; 19 | 20 | float angleFromDegrees, angleToDegrees, angleStep; 21 | float radius, textRadius, iconRadius; 22 | UIFont *font; 23 | 24 | RRCircularMenuLabel *label; 25 | 26 | RRCircularSlider *slider; 27 | } 28 | 29 | - (void) dealloc { 30 | items = _release(items); 31 | label = _release(label); 32 | slider = _release(slider); 33 | [super dealloc]; 34 | } 35 | 36 | - (id)initWithFrame:(CGRect)frame { 37 | self = [super initWithFrame:frame]; 38 | if (self) { 39 | self.backgroundColor = [UIColor clearColor]; 40 | // self.clipsToBounds = YES; 41 | [self setUpItems]; 42 | 43 | UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)]; 44 | [self addGestureRecognizer:tap]; 45 | [tap release]; 46 | } 47 | return self; 48 | } 49 | 50 | # pragma mark - Set up 51 | 52 | // Put your items here 53 | - (void) setUpItems { 54 | angleFromDegrees = -190, angleToDegrees = 10, angleStep = (angleToDegrees - angleFromDegrees) / 6; 55 | radius = 146, textRadius = 130, iconRadius = 85; 56 | font = [UIFont systemFontOfSize:14]; 57 | 58 | items = _release(items); 59 | items = [[NSMutableArray alloc] init]; 60 | float currentAngle = angleFromDegrees; 61 | int index = 0; 62 | for (NSString *title in @[@"Time", @"Distance", @"Speed", @"HR", @"Calories", @"Route"]) { 63 | [self addItem:title from:currentAngle to:currentAngle + angleStep index:index++]; 64 | currentAngle += angleStep; 65 | } 66 | [[items objectAtIndex:0] setFirstOne:YES]; 67 | [[items lastObject] setLastOne:YES]; 68 | 69 | // disable last border for symmetry 70 | [[items lastObject] setBorderWidth:0]; 71 | 72 | // Add center label 73 | label = [[RRCircularMenuLabel alloc] initWithFrame:CGRectMake(10 + (self.frame.size.width - 20) / 2 - 50, self.frame.size.height - 50 - 20, 100, 100)]; 74 | // label.text = @"EVERY\n5 min"; 75 | [self addSubview:label]; 76 | 77 | // Add slider 78 | slider = [[RRCircularSlider alloc] initWithFrame:CGRectMake(0, 10, self.bounds.size.width, self.bounds.size.width)]; 79 | slider.angleFrom = angleFromDegrees; 80 | slider.angleTo = angleToDegrees; 81 | slider.step = angleStep; 82 | [slider setValue:0 animated:NO notifying:NO]; 83 | [slider addTarget:self action:@selector(sliderValueChanged) forControlEvents:UIControlEventValueChanged]; 84 | [self addSubview:slider]; 85 | } 86 | 87 | - (void) addItem:(NSString *)title from:(float)angleFrom to:(float)angleTo index:(int)index{ 88 | RRCircularItem *item = [[RRCircularItem alloc] initWithFrame:CGRectMake(10, 0, self.frame.size.width - 20, self.frame.size.height)]; 89 | item.tag = index; 90 | item.text = title; 91 | item.radius = radius; 92 | item.textRadius = textRadius; 93 | item.iconRadius = iconRadius; 94 | item.center = CGPointMake((self.frame.size.width - 20) / 2, self.frame.size.height - 20); 95 | item.angleFrom = angleFrom; 96 | item.angleTo = angleTo; 97 | item.activeImage = [UIImage imageNamed:[[title lowercaseString] stringByAppendingString:@"@2x.png"]]; 98 | item.inactiveImage = [UIImage imageNamed:[[title lowercaseString] stringByAppendingString:@".off@2x.png"]]; 99 | [items addObject:item]; 100 | [self addSubview:item]; 101 | [item release]; 102 | } 103 | 104 | # pragma mark - Events handling 105 | 106 | - (void) sliderValueChanged { 107 | // do not notify delegate when animating 108 | if (slider.value < 0) return; 109 | 110 | if (_delegate && [_delegate respondsToSelector:@selector(sliderValueChanged:)]) [_delegate sliderValueChanged:slider]; 111 | } 112 | 113 | - (void) handleTap:(UIGestureRecognizer *)recognizer { 114 | if ([label hitInside:[recognizer locationInView:label]]) { 115 | [label setActive:!label.active]; 116 | [label setNeedsDisplay]; 117 | if (_delegate && [_delegate respondsToSelector:@selector(menuLabel:didChangeActive:)]) [_delegate menuLabel:label didChangeActive:label.active]; 118 | } else { 119 | for (RRCircularItem *item in items) { 120 | CGPoint point = [recognizer locationInView:item]; 121 | if ([item hitInside:point] && (!_delegate || ![_delegate respondsToSelector:@selector(ignoreClickFor:)] || ![_delegate ignoreClickFor:item])) { 122 | [item setActive:!item.active]; 123 | [item setNeedsDisplay]; 124 | if (_delegate && [_delegate respondsToSelector:@selector(menuItem:didChangeActive:)]) [_delegate menuItem:item didChangeActive:item.active]; 125 | } 126 | } 127 | } 128 | } 129 | 130 | # pragma mark - Getters / Setters 131 | 132 | - (void) setItem:(int)index active:(BOOL)active { 133 | [[items objectAtIndex:index] setActive:active]; 134 | [[items objectAtIndex:index] setNeedsDisplay]; 135 | } 136 | 137 | - (BOOL) isItemActive:(int)index { 138 | return [[items objectAtIndex:index] active]; 139 | } 140 | 141 | - (void) setLabelActive:(BOOL)active { 142 | label.active = active; 143 | [label setNeedsDisplay]; 144 | } 145 | 146 | - (BOOL) isLabelActive { 147 | return label.active; 148 | } 149 | 150 | - (void) setLabelText:(NSString *)text { 151 | label.text = text; 152 | [label setNeedsDisplay]; 153 | } 154 | 155 | - (void) setSliderValue:(int)value { 156 | [slider setValue:value]; 157 | } 158 | 159 | - (void) setSliderValue:(int)value animated:(BOOL)animated{ 160 | [slider setValue:value animated:animated]; 161 | } 162 | 163 | - (int) sliderValue { 164 | return slider.value; 165 | } 166 | 167 | # pragma mark - Drawing and Animations 168 | 169 | - (void) showWithAnimationBlock:(void(^)(void))block settingSliderTo:(int)value { 170 | // Animation takes four steps performed one-by-one: 171 | // 1. Bubble up label, then 2: 172 | // 2. Rotate segments around their center, then 3: 173 | // 3. Scale up rainbow, then 4: 174 | // 4. Rotate slider thumb to its position 175 | 176 | float duration1 = 0.5; 177 | float duration2 = 0.5; 178 | float duration3 = 0.5; 179 | 180 | // 1. Bubble up label 181 | self.hidden = NO; 182 | label.alpha = 0; 183 | label.transform = CGAffineTransformMakeScale(0.1, 0.1); 184 | [UIView animateWithDuration:duration1 / 2 animations:^{ 185 | label.alpha = 0.8; 186 | label.transform = CGAffineTransformMakeScale(1.2, 1.2); 187 | } completion:^(BOOL finished) { 188 | [UIView animateWithDuration:duration1 / 2 animations:^{ 189 | label.alpha = 1; 190 | label.transform = CGAffineTransformIdentity; 191 | }]; 192 | }]; 193 | 194 | // 2. Rotate segments around their center 195 | float step = M_PI / 180 * fabsf(angleFromDegrees - angleToDegrees) / items.count; 196 | for (int i = 0; i < items.count; i++) { 197 | float angle = - (i + 1) * step; 198 | RRCircularItem *item = [items objectAtIndex:i]; 199 | RRViewSetAnchorPoint(item, CGPointMake(item.center.x / item.frame.size.width, item.center.y / item.frame.size.height)); 200 | [item setTransform:CGAffineTransformMakeRotation(angle)]; 201 | [item setAlpha:0]; 202 | } 203 | [UIView animateWithDuration:duration2 204 | delay:duration1 / 3 205 | options:UIViewAnimationOptionCurveEaseInOut 206 | animations:^{ 207 | for (RRCircularItem *item in items) { 208 | [item setTransform:CGAffineTransformIdentity]; 209 | [item setAlpha:1]; 210 | } 211 | if (block) block(); 212 | } completion:^(BOOL finished) { 213 | slider.hidden = NO; 214 | }]; 215 | 216 | // 3. Scale up rainbow 217 | [slider removeFromSuperview]; 218 | [self insertSubview:slider belowSubview:[items objectAtIndex:0]]; 219 | [slider setValue:-1 animated:NO notifying:NO]; 220 | 221 | slider.alpha = 0; 222 | slider.transform = CGAffineTransformMakeScale(0.8, 0.8); 223 | [UIView animateWithDuration:duration3 * 2 / 3 224 | delay:duration1 / 3 225 | options:UIViewAnimationOptionCurveEaseIn 226 | animations:^{ 227 | slider.transform = CGAffineTransformMakeScale(1.1, 1.1); 228 | slider.alpha = 1; 229 | } completion:^(BOOL finished) { 230 | [slider removeFromSuperview]; 231 | [self addSubview:slider]; 232 | [UIView animateWithDuration:duration3 * 1 / 3 animations:^{ 233 | slider.transform = CGAffineTransformIdentity; 234 | } completion:^(BOOL finished) { 235 | // 4. Rotate slider thumb to its position 236 | [slider setValue:value animated:YES notifying:NO]; 237 | }]; 238 | }]; 239 | } 240 | 241 | - (void) hideWithAnimationBlock:(void(^)(void))block { 242 | // Reversed sequence from 'show' with faster timing 243 | 244 | float duration1 = 0.4; 245 | float duration2 = 0.4; 246 | 247 | // 1. Scale down whole slider 248 | [slider setValue:-1 animated:YES notifying:NO]; 249 | [UIView animateWithDuration:duration1 / 2 250 | delay:0 251 | options:UIViewAnimationOptionCurveEaseOut 252 | animations:^{ 253 | slider.transform = CGAffineTransformMakeScale(1.1, 1.1); 254 | slider.alpha = 0.5; 255 | } completion:^(BOOL finished) { 256 | [slider removeFromSuperview]; 257 | [self insertSubview:slider belowSubview:[items objectAtIndex:0]]; 258 | 259 | [UIView animateWithDuration:duration1 / 4 animations:^{ 260 | slider.transform = CGAffineTransformMakeScale(0.8, 0.8); 261 | slider.alpha = 0; 262 | } completion:^(BOOL finished) { 263 | slider.hidden = YES; 264 | slider.transform = CGAffineTransformIdentity; 265 | }]; 266 | }]; 267 | 268 | 269 | // 2. Rotate segments around their center 270 | float step = M_PI / 180 * fabsf(angleFromDegrees - angleToDegrees) / items.count; 271 | [UIView animateWithDuration:duration2 272 | delay:0 273 | options:UIViewAnimationOptionCurveEaseInOut 274 | animations:^{ 275 | for (int i = 0; i < items.count; i++) { 276 | RRCircularItem *item = [items objectAtIndex:i]; 277 | RRViewSetAnchorPoint(item, CGPointMake(item.center.x / item.frame.size.width, item.center.y / item.frame.size.height)); 278 | [item setTransform:CGAffineTransformMakeRotation(- (i + 1) * step)]; 279 | [item setAlpha:0]; 280 | } 281 | if (block) block(); 282 | } completion:^(BOOL finished) { 283 | // remove menu from view hierarchy here beacuse it's the latter block of all animations 284 | [self removeFromSuperview]; 285 | }]; 286 | 287 | // 3. Bubble down label 288 | [UIView animateWithDuration:duration1 / 2 289 | delay:0 290 | options:UIViewAnimationOptionCurveEaseInOut 291 | animations:^{ 292 | label.alpha = 0.8; 293 | label.transform = CGAffineTransformMakeScale(1.2, 1.2); 294 | } completion:^(BOOL finished) { 295 | [UIView animateWithDuration:duration1 / 4 animations:^{ 296 | label.alpha = 0; 297 | label.transform = CGAffineTransformMakeScale(0.1, 0.1); 298 | }]; 299 | }]; 300 | } 301 | 302 | - (void) drawRect:(CGRect)rect { 303 | [super drawRect:rect]; 304 | 305 | CGContextRef ctx = UIGraphicsGetCurrentContext(); 306 | CGContextSaveGState(ctx); 307 | 308 | // This is a circular background. We need it because circle center is a bit above bottom of the screen, 309 | // there are no segments in the lower part of circle 310 | // CGContextAddEllipseInRect(ctx, CGRectMake(10 + (self.frame.size.width - 20) / 2 - 150 + 10 + 1, self.frame.size.height - 160 + 1, 278, 278)); 311 | // CGContextSetFillColorWithColor(ctx, label.backColor.CGColor); 312 | // CGContextFillPath(ctx); 313 | 314 | CGContextRestoreGState(ctx); 315 | } 316 | 317 | @end 318 | -------------------------------------------------------------------------------- /Sources/RRCircularMenuLabel.h: -------------------------------------------------------------------------------- 1 | // 2 | // RRCircularMenuLabel.h 3 | // RRCircularMenu 4 | // 5 | // Copyright (C) 2013 Artem Salpagarov 6 | 7 | // 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: 8 | 9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | // 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. 12 | 13 | #import 14 | 15 | @interface RRCircularMenuLabel : UIView 16 | 17 | @property (nonatomic) BOOL active; 18 | 19 | @property (nonatomic, retain) UIColor *borderColor; 20 | @property (nonatomic, retain) UIColor *backColor; 21 | @property (nonatomic) float borderWidth; 22 | 23 | @property (nonatomic, retain) NSString *text; 24 | @property (nonatomic, retain) UIColor *activeTextColor; 25 | @property (nonatomic, retain) UIColor *inactiveTextColor; 26 | @property (nonatomic, retain) UIFont *font; 27 | 28 | - (BOOL) hitInside:(CGPoint)point; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /Sources/RRCircularMenuLabel.m: -------------------------------------------------------------------------------- 1 | // 2 | // RRCircularMenuLabel.m 3 | // RRCircularMenu 4 | // 5 | // Copyright (C) 2013 Artem Salpagarov 6 | 7 | // 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: 8 | 9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | // 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. 12 | 13 | #import "RRCircularMenuLabel.h" 14 | #import "Helpers.h" 15 | 16 | @implementation RRCircularMenuLabel 17 | 18 | @synthesize active = _active, backColor = _backColor, borderColor = _borderColor, borderWidth = _borderWidth, activeTextColor = _activeTextColor, inactiveTextColor = _inactiveTextColor, font = _font, text = _text; 19 | 20 | - (id)initWithFrame:(CGRect)frame { 21 | self = [super initWithFrame:frame]; 22 | if (self) { 23 | self.backgroundColor = [UIColor clearColor]; 24 | self.font = [UIFont boldSystemFontOfSize:16]; 25 | self.backColor = [UIColor colorWithRed:242.0/255 green:242.0/255 blue:242.0/255 alpha:1]; // #F2F2F2 26 | self.borderColor = [UIColor colorWithRed:219.0/255 green:219.0/255 blue:219.0/255 alpha:1]; // #DBDBDB 27 | self.inactiveTextColor = [UIColor colorWithRed:219.0/255 green:219.0/255 blue:219.0/255 alpha:1]; // #DBDBDB 28 | self.activeTextColor = [UIColor colorWithRed:51.0/255 green:51.0/255 blue:51.0/255 alpha:1]; // #333333 29 | self.font = [UIFont boldSystemFontOfSize:16]; 30 | self.borderWidth = 2; 31 | } 32 | return self; 33 | } 34 | 35 | - (void) dealloc { 36 | _backColor = _release(_backColor); 37 | _borderColor = _release(_borderColor); 38 | _inactiveTextColor = _release(_inactiveTextColor); 39 | _activeTextColor = _release(_activeTextColor); 40 | _font = _release(_font); 41 | [super dealloc]; 42 | } 43 | 44 | - (void) drawRect:(CGRect)rect { 45 | CGContextRef ctx = UIGraphicsGetCurrentContext(); 46 | CGContextSaveGState(ctx); 47 | 48 | CGContextSetStrokeColorWithColor(ctx, _borderColor.CGColor); 49 | CGContextSetFillColorWithColor(ctx, _backColor.CGColor); 50 | CGContextSetLineWidth(ctx, _borderWidth); 51 | 52 | CGContextAddEllipseInRect(ctx, self.bounds); 53 | CGContextFillPath(ctx); 54 | 55 | // We need to decrease ellipse size to fit border in bounds 56 | float shrink = _borderWidth > 2 ? _borderWidth / 2 : 1; 57 | CGContextAddEllipseInRect(ctx, CGRectAdd(self.bounds, shrink, shrink, -2 * shrink, -2 * shrink)); 58 | CGContextStrokePath(ctx); 59 | 60 | CGSize size = [_text sizeWithFont:_font constrainedToSize:CGSizeMake(self.bounds.size.width, self.bounds.size.height - 12) lineBreakMode:NSLineBreakByWordWrapping]; 61 | [_active ? _activeTextColor : _inactiveTextColor setFill]; 62 | [_text drawInRect:CGRectMake((self.bounds.size.width - size.width) / 2, (self.bounds.size.height - size.height) / 2 - 12, size.width, size.height) 63 | withFont:_font 64 | lineBreakMode:NSLineBreakByWordWrapping 65 | alignment:NSTextAlignmentCenter]; 66 | CGContextFillPath(ctx); 67 | 68 | CGContextRestoreGState(ctx); 69 | } 70 | 71 | - (BOOL) hitInside:(CGPoint)point { 72 | return hypot(point.x - CGRectGetMidX(self.bounds), point.y - CGRectGetMidY(self.bounds)) < self.bounds.size.width / 2; 73 | } 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /Sources/RRCircularSlider.h: -------------------------------------------------------------------------------- 1 | // 2 | // RRCircularSlider.h 3 | // RRCircularMenu 4 | // 5 | // Copyright (C) 2013 Artem Salpagarov 6 | 7 | // 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: 8 | 9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | // 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. 12 | 13 | #import 14 | 15 | #define SLIDER_WIDTH 4 16 | 17 | @interface RRCircularSlider : UIControl 18 | 19 | // These are not configurable, actually. For now it works only for -M_PI .. 0. 20 | @property (nonatomic) float angleFrom, angleTo; 21 | // Angle for one step of thumb 22 | @property (nonatomic) float step; 23 | 24 | // Number of steps from angleFrom 25 | @property (nonatomic) int value; 26 | 27 | - (void) setValue:(int)value animated:(BOOL)animated; // notifying = YES 28 | - (void) setValue:(int)value animated:(BOOL)animated notifying:(BOOL)notifying; // notify listeners about value changes 29 | 30 | - (UIView *) thumb; 31 | @end 32 | -------------------------------------------------------------------------------- /Sources/RRCircularSlider.m: -------------------------------------------------------------------------------- 1 | // 2 | // RRCircularSlider.m 3 | // RRCircularMenu 4 | // 5 | // Copyright (C) 2013 Artem Salpagarov 6 | 7 | // 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: 8 | 9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | // 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. 12 | 13 | // Thanks to https://github.com/paiv for https://github.com/paiv/AngleGradientLayer 14 | 15 | #import "RRCircularSlider.h" 16 | #import "Helpers.h" 17 | #import 18 | 19 | @implementation RRCircularSlider { 20 | CGImageRef gradient; 21 | UIButton *thumb; 22 | 23 | BOOL inThumb; 24 | 25 | float radius; 26 | float offset; 27 | CGPoint center; 28 | CGSize circleSize; 29 | } 30 | 31 | @synthesize angleFrom = _angleFrom, angleTo = _angleTo, step = _step, value = _value; 32 | 33 | - (void) dealloc { 34 | CGImageRelease(gradient); 35 | [super dealloc]; 36 | } 37 | 38 | - (id)initWithFrame:(CGRect)frame { 39 | self = [super initWithFrame:frame]; 40 | if (self) { 41 | offset = 12; 42 | radius = frame.size.width / 2 - offset; 43 | center = CGPointMake(offset + radius, offset + radius); 44 | 45 | self.backgroundColor = [UIColor clearColor]; 46 | 47 | NSMutableArray *colors = [[NSMutableArray alloc] init]; 48 | NSMutableArray *locations = [[NSMutableArray alloc] init]; 49 | 50 | [colors addObject:(id)[UIColor colorWithRed:0.988 green:0.949 blue:0.298 alpha:1].CGColor]; 51 | [colors addObject:(id)[UIColor colorWithRed:0.000 green:0.718 blue:0.271 alpha:1].CGColor]; 52 | [colors addObject:(id)[UIColor colorWithRed:0.000 green:0.882 blue:0.976 alpha:1].CGColor]; 53 | [colors addObject:(id)[UIColor colorWithRed:0.000 green:0.882 blue:0.976 alpha:1].CGColor]; 54 | [colors addObject:(id)[UIColor colorWithRed:0.000 green:0.718 blue:0.271 alpha:1].CGColor]; 55 | [colors addObject:(id)[UIColor colorWithRed:0.988 green:0.949 blue:0.298 alpha:1].CGColor]; 56 | 57 | [locations addObject:[NSNumber numberWithFloat:0]]; 58 | [locations addObject:[NSNumber numberWithFloat:0.1666]]; 59 | [locations addObject:[NSNumber numberWithFloat:0.3333]]; 60 | [locations addObject:[NSNumber numberWithFloat:0.6666]]; 61 | [locations addObject:[NSNumber numberWithFloat:0.8333]]; 62 | [locations addObject:[NSNumber numberWithFloat:1]]; 63 | 64 | CGImageRef fullGradient = [self newImageGradientInRect:self.bounds colors:colors locations:locations]; 65 | CGImageRef maskRef = [self borderMask]; 66 | 67 | CGImageRef mask = CGImageMaskCreate(CGImageGetWidth(maskRef), 68 | CGImageGetHeight(maskRef), 69 | CGImageGetBitsPerComponent(maskRef), 70 | CGImageGetBitsPerPixel(maskRef), 71 | CGImageGetBytesPerRow(maskRef), 72 | CGImageGetDataProvider(maskRef), NULL, false); 73 | gradient = CGImageCreateWithMask(fullGradient, mask); 74 | 75 | CGImageRelease(mask); 76 | CGImageRelease(fullGradient); 77 | 78 | [colors release]; 79 | [locations release]; 80 | 81 | 82 | UIImage *thumbImage = [self thumbImage]; 83 | thumb = [[UIButton alloc] initWithFrame:CGRectZero]; 84 | [thumb setImage:thumbImage forState:UIControlStateNormal]; 85 | [thumb setImage:thumbImage forState:UIControlStateSelected]; 86 | [thumb setImage:thumbImage forState:UIControlStateHighlighted]; 87 | [thumb addTarget:self action:@selector(thumbPressed) forControlEvents:UIControlEventTouchDragInside]; 88 | [self addSubview:thumb]; 89 | 90 | // RRViewSetAnchorPoint(thumb, CGPointMake(0.5, <#CGFloat y#>)) 91 | 92 | 93 | UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)]; 94 | [self addGestureRecognizer:pan]; 95 | [pan release]; 96 | } 97 | return self; 98 | } 99 | 100 | # pragma mark - Events handling 101 | 102 | - (void) thumbPressed { 103 | inThumb = YES; 104 | } 105 | 106 | - (void) handlePan:(UIPanGestureRecognizer *)pan { 107 | if (!inThumb) return; 108 | 109 | CGPoint point = [pan locationInView:self]; 110 | switch (pan.state) { 111 | case UIGestureRecognizerStateChanged: { 112 | // CGPoint center = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2 - 10); 113 | float x = point.x - self.bounds.origin.x - center.x; 114 | float y = -point.y + self.bounds.origin.y + center.y; 115 | float hypot = hypotf(x, y); 116 | 117 | float angle; 118 | if (x / hypot > 0) { 119 | angle = - asinf(y / hypot); 120 | } else if (x / hypot < 0) { 121 | angle = asinf(y / hypot) - M_PI; 122 | } else { 123 | angle = - M_PI_2; 124 | } 125 | 126 | if (angle < _angleFrom) angle = _angleFrom + _step / 2; 127 | if (angle > _angleTo) angle = _angleTo - _step / 2; 128 | 129 | angle = fabs(_angleFrom - angle) - _step / 2; 130 | self.value = roundf(angle / _step); 131 | 132 | break; 133 | } 134 | case UIGestureRecognizerStateEnded: 135 | case UIGestureRecognizerStateCancelled: 136 | inThumb = NO; 137 | break; 138 | default: 139 | break; 140 | } 141 | 142 | } 143 | 144 | # pragma mark - Getters and setters 145 | 146 | - (void) setAngleTo:(float)angleTo { 147 | _angleTo = M_PI * angleTo / 180; 148 | } 149 | 150 | - (void) setAngleFrom:(float)angleFrom { 151 | _angleFrom = M_PI * angleFrom / 180; 152 | } 153 | 154 | - (void) setStep:(float)step { 155 | _step = M_PI * step / 180; 156 | } 157 | 158 | - (void) setValue:(int)value { 159 | [self setValue:value animated:YES]; 160 | } 161 | 162 | - (CGFloat) angleBetweenThreePoints:(CGPoint)centerPoint p1:(CGPoint)p1 p2:(CGPoint)p2 { 163 | CGPoint v1 = CGPointMake(p1.x - centerPoint.x, p1.y - centerPoint.y); 164 | CGPoint v2 = CGPointMake(p2.x - centerPoint.x, p2.y - centerPoint.y); 165 | 166 | CGFloat angle = atan2f(v2.x*v1.y - v1.x*v2.y, v1.x*v2.x + v1.y*v2.y); 167 | 168 | return angle; 169 | } 170 | 171 | - (void) setValue:(int)value animated:(BOOL)animated { 172 | [self setValue:value animated:animated notifying:YES]; 173 | } 174 | 175 | - (void) setValue:(int)value animated:(BOOL)animated notifying:(BOOL)notifying { 176 | if (_value == value && animated) return; 177 | 178 | int change = value - _value; 179 | _value = value; 180 | 181 | float x = offset + (1 + cosf(_angleFrom + (value + 0.5) * _step)) * radius; 182 | float y = offset + (1 + sinf(_angleFrom + (value + 0.5) * _step)) * radius - 10; 183 | 184 | if (animated) { 185 | float currentAngle = [self angleBetweenThreePoints:CGPointMake(center.x, center.y - 10) 186 | p1:CGPointMake(thumb.frame.origin.x + 20, thumb.frame.origin.y + 20) 187 | p2:CGPointMake(center.x + radius, center.y - 10)]; 188 | float newAngle = [self angleBetweenThreePoints:CGPointMake(center.x, center.y - 10) 189 | p1:CGPointMake(x, y) 190 | p2:CGPointMake(center.x + radius, center.y - 10)]; 191 | 192 | CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"]; 193 | 194 | CGMutablePathRef path = CGPathCreateMutable(); 195 | CGPathAddArc(path, nil, center.x, center.y - 10, radius, currentAngle, newAngle, change < 0); 196 | animation.path=path; 197 | CGPathRelease(path); 198 | 199 | // set the animation properties 200 | animation.duration = 0.2; 201 | animation.removedOnCompletion = NO; 202 | animation.autoreverses = NO; 203 | animation.rotationMode = kCAAnimationRotateAutoReverse; 204 | animation.fillMode = kCAFillModeForwards; 205 | animation.delegate = self; 206 | [animation setValue:[NSString stringWithFormat:@"%f,%f", x, y] forKey:@"x,y"]; 207 | 208 | [thumb.layer addAnimation:animation forKey:@"position"]; 209 | } else { 210 | thumb.frame = CGRectMake(x - 20, y - 20, 40, 40); 211 | } 212 | if (notifying) [self sendActionsForControlEvents:UIControlEventValueChanged]; 213 | } 214 | 215 | - (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag { 216 | NSArray *comps = [[theAnimation valueForKey:@"x,y"] componentsSeparatedByString:@","]; 217 | [thumb.layer removeAnimationForKey:@"position"]; 218 | thumb.frame = CGRectMake([[comps objectAtIndex:0] floatValue] - 20, [[comps objectAtIndex:1] floatValue] - 20, 40, 40); 219 | } 220 | 221 | - (UIView *) thumb { return thumb; } 222 | 223 | # pragma mark - Drawing 224 | 225 | - (void)drawRect:(CGRect)rect { 226 | CGContextRef ctx = UIGraphicsGetCurrentContext(); 227 | CGContextSaveGState(ctx); 228 | 229 | CGContextTranslateCTM(ctx, center.x, center.y); 230 | // CGContextRotateCTM(ctx, M_PI_4); 231 | CGContextDrawImage(ctx, CGRectAdd(self.bounds, -self.bounds.size.width / 2, - self.bounds.size.height / 2 - 10, 0, 0), gradient); 232 | // CGContextDrawImage(ctx, self.bounds, [self borderMask]); 233 | 234 | CGContextRestoreGState(ctx); 235 | } 236 | 237 | - (CGImageRef) borderMask { 238 | UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 1); 239 | 240 | CGContextRef ctx = UIGraphicsGetCurrentContext(); 241 | CGContextSaveGState(ctx); 242 | 243 | CGContextAddRect(ctx, self.bounds); 244 | CGContextSetFillColorWithColor(ctx, [UIColor whiteColor].CGColor); 245 | CGContextFillPath(ctx); 246 | 247 | CGContextSetStrokeColorWithColor(ctx, [UIColor blackColor].CGColor); 248 | CGContextSetLineWidth(ctx, SLIDER_WIDTH); 249 | CGContextAddEllipseInRect(ctx, CGRectMake(offset, offset, 2 * radius, 2 * radius)); 250 | CGContextStrokePath(ctx); 251 | 252 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 253 | 254 | // Clean up 255 | UIGraphicsEndImageContext(); // Clean up 256 | 257 | return image.CGImage; 258 | } 259 | 260 | - (UIImage *) thumbImage { 261 | UIGraphicsBeginImageContextWithOptions(CGSizeMake(40, 40), NO, 1); 262 | 263 | CGContextRef ctx = UIGraphicsGetCurrentContext(); 264 | 265 | CGContextAddEllipseInRect(ctx, CGRectMake(8, 8, 24, 24)); 266 | CGContextSetFillColorWithColor(ctx, [UIColor colorWithWhite:0.98 alpha:1].CGColor); 267 | CGContextFillPath(ctx); 268 | 269 | CGContextSetShadowWithColor(ctx, CGSizeZero, 3, [UIColor colorWithWhite:0 alpha:0.2].CGColor); 270 | CGContextSetStrokeColorWithColor(ctx, [UIColor colorWithWhite:0.775 alpha:1].CGColor); 271 | CGContextSetLineWidth(ctx, 1); 272 | CGContextAddEllipseInRect(ctx, CGRectMake(8, 8, 24, 24)); 273 | CGContextStrokePath(ctx); 274 | 275 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 276 | 277 | // Clean up 278 | UIGraphicsEndImageContext(); // Clean up 279 | 280 | return image; 281 | } 282 | 283 | #pragma mark - https://github.com/paiv/AngleGradientLayer 284 | 285 | #define byte unsigned char 286 | #define uint unsigned int 287 | #define F2CC(x) ((byte)(255 * x)) 288 | #define RGBAF(r,g,b,a) (F2CC(r) << 24 | F2CC(g) << 16 | F2CC(b) << 8 | F2CC(a)) 289 | #define RGBA(r,g,b,a) ((byte)r << 24 | (byte)g << 16 | (byte)b << 8 | (byte)a) 290 | #define RGBA_R(c) ((uint)c >> 24 & 255) 291 | #define RGBA_G(c) ((uint)c >> 16 & 255) 292 | #define RGBA_B(c) ((uint)c >> 8 & 255) 293 | #define RGBA_A(c) ((uint)c >> 0 & 255) 294 | 295 | static void angleGradient(byte* data, int w, int h, int* colors, int colorCount, float* locations, int locationCount); 296 | 297 | - (CGImageRef)newImageGradientInRect:(CGRect)rect colors:(NSArray *)colorsArray locations:(NSArray *)locationsArray{ 298 | int w = CGRectGetWidth(rect); 299 | int h = CGRectGetHeight(rect); 300 | int bitsPerComponent = 8; 301 | int bpp = 4 * bitsPerComponent / 8; 302 | int byteCount = w * h * bpp; 303 | 304 | int colorCount = colorsArray.count; 305 | int locationCount = 0; 306 | int* colors = NULL; 307 | float* locations = NULL; 308 | 309 | if (colorCount > 0) { 310 | colors = calloc(colorCount, bpp); 311 | int *p = colors; 312 | for (id cg in colorsArray) { 313 | float r, g, b, a; 314 | UIColor *c = [[UIColor alloc] initWithCGColor:(CGColorRef)cg]; 315 | if (![c getRed:&r green:&g blue:&b alpha:&a]) { 316 | if (![c getWhite:&r alpha:&a]) { 317 | [c release]; 318 | continue; 319 | } 320 | g = b = r; 321 | } 322 | [c release]; 323 | *p++ = RGBAF(r, g, b, a); 324 | } 325 | } 326 | if (locationsArray.count > 0 && locationsArray.count == colorCount) { 327 | locationCount = locationsArray.count; 328 | locations = calloc(locationCount, sizeof(locations[0])); 329 | float *p = locations; 330 | for (NSNumber *n in locationsArray) { 331 | *p++ = [n floatValue]; 332 | } 333 | } 334 | 335 | byte* data = malloc(byteCount); 336 | angleGradient(data, w, h, colors, colorCount, locations, locationCount); 337 | 338 | if (colors) free(colors); 339 | if (locations) free(locations); 340 | 341 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 342 | CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Little; 343 | CGContextRef ctx = CGBitmapContextCreate(data, w, h, bitsPerComponent, w * bpp, colorSpace, bitmapInfo); 344 | CGColorSpaceRelease(colorSpace); 345 | CGImageRef img = CGBitmapContextCreateImage(ctx); 346 | CGContextRelease(ctx); 347 | free(data); 348 | return img; 349 | } 350 | 351 | @end 352 | 353 | static inline byte blerp(byte a, byte b, float w) 354 | { 355 | return a + w * (b - a); 356 | } 357 | static inline int lerp(int a, int b, float w) 358 | { 359 | return RGBA(blerp(RGBA_R(a), RGBA_R(b), w), 360 | blerp(RGBA_G(a), RGBA_G(b), w), 361 | blerp(RGBA_B(a), RGBA_B(b), w), 362 | blerp(RGBA_A(a), RGBA_A(b), w)); 363 | } 364 | 365 | void angleGradient(byte* data, int w, int h, int* colors, int colorCount, float* locations, int locationCount) 366 | { 367 | if (colorCount < 1) return; 368 | if (locationCount > 0 && locationCount != colorCount) return; 369 | 370 | int* p = (int*)data; 371 | float centerX = (float)w / 2; 372 | float centerY = (float)h / 2; 373 | 374 | for (int y = 0; y < h; y++) 375 | for (int x = 0; x < w; x++) { 376 | float dirX = x - centerX; 377 | float dirY = y - centerY; 378 | float angle = atan2f(dirY, dirX); 379 | if (dirY < 0) angle += 2 * M_PI; 380 | angle /= 2 * M_PI; 381 | 382 | int index = 0, nextIndex = 0; 383 | float t = 0; 384 | 385 | if (locationCount > 0) { 386 | for (index = locationCount - 1; index >= 0; index--) { 387 | if (angle >= locations[index]) { 388 | break; 389 | } 390 | } 391 | if (index >= locationCount) index = locationCount - 1; 392 | nextIndex = index + 1; 393 | if (nextIndex >= locationCount) nextIndex = locationCount - 1; 394 | float ld = locations[nextIndex] - locations[index]; 395 | t = ld <= 0 ? 0 : (angle - locations[index]) / ld; 396 | } 397 | else { 398 | t = angle * (colorCount - 1); 399 | index = t; 400 | t -= index; 401 | nextIndex = index + 1; 402 | if (nextIndex >= colorCount) nextIndex = colorCount - 1; 403 | } 404 | 405 | int lc = colors[index]; 406 | int rc = colors[nextIndex]; 407 | int color = lerp(lc, rc, t); 408 | *p++ = color; 409 | } 410 | } 411 | 412 | 413 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iartem/RRCircularMenu/e320a67774b3ba04638a6fcec1230ff8738715d3/screenshot.png --------------------------------------------------------------------------------