1/************************************************************************ 2 ************************************************************************ 3 FAUST Architecture File 4 Copyright (C) 2003-2012 GRAME, Centre National de Creation Musicale 5 --------------------------------------------------------------------- 6 7 This is sample code. This file is provided as an example of minimal 8 FAUST architecture file. Redistribution and use in source and binary 9 forms, with or without modification, in part or in full are permitted. 10 In particular you can create a derived work of this FAUST architecture 11 and distribute that work under terms of your choice. 12 13 This sample code is distributed in the hope that it will be useful, 14 but WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 16 ************************************************************************ 17 ************************************************************************/ 18 19/************************************************************************ 20 ************************************************************************ 21 Based on DCControls - https://github.com/domesticcatsoftware/DCControls 22 Copyright (C) 2011 by Patrick Richards - http://domesticcat.com.au/ 23 24 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 30 THE SOFTWARE. 31 ************************************************************************ 32 ************************************************************************/ 33 34#import "FIKnob.h" 35 36#define kStdKnobHintSpace 10 37 38@implementation FIKnob 39@synthesize biDirectional, arcStartAngle, cutoutSize, valueArcWidth; 40@synthesize doubleTapValue, tripleTapValue; 41 42#pragma mark - 43#pragma mark Init 44 45- (id)initWithDelegate:(id)aDelegate 46{ 47 if ((self = [super initWithDelegate:aDelegate])) 48 { 49 _hint = nil; 50 self.arcStartAngle = 90.0; 51 self.cutoutSize = 60.0; 52 53 // add the gesture recognizers for double & triple taps 54 UITapGestureRecognizer *doubleTapGesture = [[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)] autorelease]; 55 doubleTapGesture.numberOfTapsRequired = 2; 56 [self addGestureRecognizer:doubleTapGesture]; 57 58 UITapGestureRecognizer *tripleTapGesture = [[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tripleTap:)] autorelease]; 59 tripleTapGesture.numberOfTapsRequired = 3; 60 [self addGestureRecognizer:tripleTapGesture]; 61 } 62 63 return self; 64} 65 66- (void)dealloc 67{ 68 if (_hint) [_hint dealloc]; 69 [super dealloc]; 70} 71 72// overridden to make sure the frame is always square. 73- (void)setFrame:(CGRect)frame 74{ 75 if (frame.size.width != frame.size.height) 76 { 77 if (frame.size.width > frame.size.height) 78 frame = CGRectMake(frame.origin.x, frame.origin.y, frame.size.width, frame.size.width); 79 else 80 frame = CGRectMake(frame.origin.x, frame.origin.y, frame.size.height, frame.size.height); 81 } 82 83 [super setFrame:frame]; 84} 85 86#pragma mark - 87#pragma mark Gestures 88 89- (void)doubleTap:(UIGestureRecognizer *)gestureRecognizer 90{ 91 if (self.allowsGestures) 92 { 93 CGPoint thisPoint = [gestureRecognizer locationInView:self]; 94 CGFloat newValue = [self valueFromPoint:thisPoint]; 95 96 if (newValue > self.value) 97 { 98 self.value = self.value + self.step; 99 } 100 else if (newValue < self.value) 101 { 102 self.value = self.value - self.step; 103 } 104 } 105} 106 107- (void)tripleTap:(UIGestureRecognizer *)gestureRecognizer 108{ 109} 110 111- (void)setValueFromGesture:(NSNumber *)newValue 112{ 113 self.value = [newValue floatValue]; 114} 115 116#pragma mark - 117#pragma mark Touch Handling 118 119- (void)updateHint 120{ 121 if (_hint) 122 { 123 UIView* scrollView = self.superview.superview.superview; 124 CGRect absHandleRect = [self convertRect:CGRectMake(0.f, 0.f, self.frame.size.width, self.frame.size.height) 125 toView:scrollView]; 126 127 _hint.title = [NSString stringWithFormat:@"%2.1f%@", self.value, self.suffixe]; 128 129 // Top 130 if (absHandleRect.origin.y >= _hint.frame.size.height + kStdKnobHintSpace 131 && absHandleRect.origin.x + (absHandleRect.size.width - _hint.frame.size.width) / 2.f + _hint.frame.size.width <= scrollView.frame.size.width 132 && absHandleRect.origin.x + (absHandleRect.size.width - _hint.frame.size.width) / 2.f >= 0.f) 133 { 134 _hint.position = 0; 135 [_hint setFrame:CGRectMake(absHandleRect.origin.x + (absHandleRect.size.width - _hint.frame.size.width) / 2.f, 136 absHandleRect.origin.y - _hint.frame.size.height - kStdKnobHintSpace, 137 _hint.frame.size.width, 138 _hint.frame.size.height)]; 139 } 140 141 // Left 142 else if (absHandleRect.origin.x >= _hint.frame.size.width + kStdKnobHintSpace) 143 { 144 _hint.position = 2; 145 [_hint setFrame:CGRectMake(absHandleRect.origin.x - _hint.frame.size.width - kStdKnobHintSpace, 146 absHandleRect.origin.y, 147 _hint.frame.size.width, 148 _hint.frame.size.height)]; 149 } 150 151 // Right 152 else if (scrollView.frame.size.width - absHandleRect.origin.x - absHandleRect.size.width >= _hint.frame.size.width + kStdKnobHintSpace) 153 { 154 _hint.position = 3; 155 [_hint setFrame:CGRectMake(absHandleRect.origin.x + absHandleRect.size.width + kStdKnobHintSpace, 156 absHandleRect.origin.y, 157 _hint.frame.size.width, 158 _hint.frame.size.height)]; 159 } 160 161 // Bottom 162 else 163 { 164 _hint.position = 1; 165 [_hint setFrame:CGRectMake(absHandleRect.origin.x + (absHandleRect.size.width - _hint.frame.size.width) / 2.f, 166 absHandleRect.origin.y + absHandleRect.size.height + kStdKnobHintSpace, 167 _hint.frame.size.width, 168 _hint.frame.size.height)]; 169 } 170 171 [_hint setNeedsDisplay]; 172 } 173} 174 175- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 176{ 177 self.motionBlocked = YES; 178 179 CGPoint thisPoint = [[touches anyObject] locationInView:self]; 180 CGPoint centerPoint = CGPointMake(self.frame.size.width / 2.0, self.frame.size.width / 2.0); 181 initialAngle = angleBetweenPoints(thisPoint, centerPoint); 182 183 // create the initial angle and initial transform 184 initialTransform = [self initialTransform]; 185 186 if (!_hint) 187 { 188 _hint = [[FIHint alloc] init]; 189 [self.superview.superview.superview addSubview:_hint]; 190 } 191 192 if (_hint) 193 { 194 _hint.title = [NSString stringWithFormat:@"%2.1f%@", self.value, self.suffixe]; 195 [self updateHint]; 196 } 197} 198 199- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 200{ 201 self.motionBlocked = YES; 202 203 CGPoint thisPoint = [[touches anyObject] locationInView:self]; 204 CGPoint centerPoint = CGPointMake(self.frame.size.width / 2.0, self.frame.size.width / 2.0); 205 206 CGFloat currentAngle = angleBetweenPoints(thisPoint, centerPoint); 207 CGFloat angleDiff = (initialAngle - currentAngle); 208 CGAffineTransform newTransform = CGAffineTransformRotate(initialTransform, angleDiff); 209 210 CGFloat newValue = [self newValueFromTransform:newTransform]; 211 212 // only set the new value if it doesn't flip the knob around 213 CGFloat diff = self.value - newValue; 214 diff = (diff < 0) ? -diff : diff; 215 if (diff < (self.max - self.min) / 10.0) 216 { 217 self.value = newValue; 218 } 219 else 220 { 221 // reset the initial angle & transform using the current value 222 initialTransform = [self initialTransform]; 223 initialAngle = angleBetweenPoints(thisPoint, centerPoint); 224 } 225 226 [self updateHint]; 227} 228 229- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 230{ 231 self.motionBlocked = NO; 232 233 if (_hint) 234 { 235 [_hint removeFromSuperview]; 236 [_hint release]; 237 _hint = nil; 238 } 239} 240 241- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event 242{ 243 self.motionBlocked = NO; 244 245 if (_hint) 246 { 247 [_hint removeFromSuperview]; 248 [_hint release]; 249 _hint = nil; 250 } 251} 252 253#pragma mark - 254#pragma mark Helper Methods 255 256- (CGFloat)valueFromPoint:(CGPoint)point 257{ 258 CGPoint centerPoint = CGPointMake(self.frame.size.width / 2.0, self.frame.size.width / 2.0); 259 CGFloat currentAngle = 450.f + angleBetweenPoints(point, centerPoint) * 57.29577951308232; 260 int normAngle = 360 - ((int)floor(currentAngle) % 360); 261 CGFloat newValue; 262 float course = 360.f - self.cutoutSize; 263 float courseOffset = self.cutoutSize / 2.f; 264 265 newValue = normAngle - courseOffset; 266 if (newValue < 0.f) newValue = 0.f; 267 else if (newValue > course) newValue = course; 268 269 newValue = newValue / course; 270 newValue = self.min + newValue * (self.max - self.min); 271 272 return newValue; 273} 274 275 276- (CGAffineTransform)initialTransform 277{ 278 CGFloat pValue = (self.value - self.min) / (self.max - self.min); 279 pValue = (pValue * kDCKnobRatio * 2) - kDCKnobRatio; 280 return CGAffineTransformMakeRotation(pValue); 281} 282 283- (CGFloat)newValueFromTransform:(CGAffineTransform)transform 284{ 285 CGFloat newValue = atan2(transform.b, transform.a); 286 newValue = (newValue + kDCKnobRatio) / (kDCKnobRatio * 2); 287 newValue = self.min + (newValue * (self.max - self.min)); 288 return newValue; 289} 290 291#pragma mark - 292#pragma mark Drawing 293 294- (void)drawRect:(CGRect)rect 295{ 296 if (self.hideOnGUI) return; 297 298 CGContextRef context = UIGraphicsGetCurrentContext(); 299 CGRect boundsRect = self.bounds; 300 CGFloat maxHalf = self.min + (self.max - self.min) / 2; 301 float x = boundsRect.size.width / 2; 302 float y = boundsRect.size.height / 2; 303 304 CGContextSaveGState(context); 305 CGContextSetLineWidth(context, self.valueArcWidth); 306 307 self.backgroundColor = [UIColor blackColor]; 308 if (self.backgroundColorAlpha > 0.02) 309 { 310 // outline semi circle 311 UIColor *backgroundColor = [UIColor colorWithRed:0.3 green:0.3 blue:0.3 alpha:1.]; 312 [backgroundColor set]; 313 314 CGContextAddArc(context, 315 x, 316 y, 317 (boundsRect.size.width / 2) - self.valueArcWidth / 2, 318 FIDegreesToRadians(self.arcStartAngle + self.cutoutSize / 2), 319 FIDegreesToRadians(self.arcStartAngle + 360 - self.cutoutSize / 2), 320 0); 321 CGContextStrokePath(context); 322 } 323 324 // Gradient 325 context = UIGraphicsGetCurrentContext(); 326 327 UIColor *lightGradientColor = [UIColor colorWithRed:0.7 green:0.7 blue:0.7 alpha:1.]; 328 UIColor *darkGradientColor = [UIColor colorWithRed:0. green:0. blue:0. alpha:1.]; 329 330 CGFloat locations[2] = {0.0, 1.0}; 331 CFArrayRef colors = (CFArrayRef) [NSArray arrayWithObjects:(id)lightGradientColor.CGColor, 332 (id)darkGradientColor.CGColor, 333 nil]; 334 335 CGColorSpaceRef colorSpc = CGColorSpaceCreateDeviceRGB(); 336 CGGradientRef gradient = CGGradientCreateWithColors(colorSpc, colors, locations); 337 338 CGContextSetBlendMode(context, kCGBlendModeMultiply); 339 340 CGContextDrawLinearGradient(context, 341 gradient, 342 CGPointMake(0.0, 0.0), 343 CGPointMake(rect.size.width, rect.size.height), 344 kCGGradientDrawsAfterEndLocation); //Adjust second point according to your view height 345 346 CGColorSpaceRelease(colorSpc); 347 CGGradientRelease(gradient); 348 CGContextSetBlendMode(context, kCGBlendModeNormal); 349 // End gradient 350 351 // draw the value semi circle 352 [self.color set]; 353 CGFloat valueAdjusted = (self.value - self.min) / (self.max - self.min); 354 if (self.biDirectional) 355 { 356 CGContextAddArc(context, 357 x, 358 y, 359 (boundsRect.size.width / 2) - self.valueArcWidth / 2, 360 FIDegreesToRadians(self.arcStartAngle + 180), 361 FIDegreesToRadians(self.arcStartAngle + self.cutoutSize / 2 + (360 - self.cutoutSize) * valueAdjusted), 362 self.value <= maxHalf); 363 } 364 else 365 { 366 CGContextAddArc(context, 367 x, 368 y, 369 (boundsRect.size.width / 2) - self.valueArcWidth / 2, 370 FIDegreesToRadians(self.arcStartAngle + self.cutoutSize / 2), 371 FIDegreesToRadians(self.arcStartAngle + self.cutoutSize / 2 + (360 - self.cutoutSize) * valueAdjusted), 372 0); 373 } 374 CGContextStrokePath(context); 375 376 // draw the value string as needed 377 if (self.displaysValue) 378 { 379 if (self.labelColor) 380 [self.labelColor set]; 381 else 382 [self.color set]; 383 NSString *valueString = nil; 384 float multiplier = 1.f; 385 386 if ([self.suffixe compare:@""] == NSOrderedSame) 387 { 388 if (self.step < 0.01) valueString = [NSString stringWithFormat:@"%2.3f", self.value]; 389 else if (self.step < 0.1) valueString = [NSString stringWithFormat:@"%2.2f", self.value]; 390 else valueString = [NSString stringWithFormat:@"%2.1f", self.value]; 391 } 392 else 393 { 394 if (self.step < 0.01) valueString = [NSString stringWithFormat:@"%2.3f\r%@", self.value, self.suffixe]; 395 else if (self.step < 0.1) valueString = [NSString stringWithFormat:@"%2.2f\r%@", self.value, self.suffixe]; 396 else valueString = [NSString stringWithFormat:@"%2.1f\r%@", self.value, self.suffixe]; 397 multiplier = 2.f; 398 } 399 CGSize valueStringSize = [valueString sizeWithFont:self.labelFont 400 forWidth:boundsRect.size.width 401 lineBreakMode:NSLineBreakByTruncatingTail]; 402 [valueString drawInRect:CGRectMake(floorf((boundsRect.size.width - valueStringSize.width) / 2.0 + self.labelOffset.x), 403 floorf((boundsRect.size.height - valueStringSize.height) / 2.0 + self.labelOffset.y), 404 valueStringSize.width, 405 multiplier * valueStringSize.height) 406 withFont:self.labelFont 407 lineBreakMode:NSLineBreakByTruncatingTail 408 alignment:NSTextAlignmentCenter]; 409 } 410 411 // Draw assignation 412 if (self.assignated) 413 { 414 CGContextSetLineWidth(context, 3.); 415 [self.color set]; 416 [self context:context addRoundedRect:boundsRect cornerRadius:3.f]; 417 CGContextStrokePath(context); 418 } 419 420 // Draw selection 421 if (self.responderSelected) 422 { 423 CGContextSetLineWidth(context, 15.); 424 [self.color set]; 425 [self context:context addRoundedRect:boundsRect cornerRadius:3.f]; 426 CGContextStrokePath(context); 427 } 428 429 CGContextRestoreGState(context); 430} 431 432@end 433