00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023 @import "CPControl.j"
00024
00025 #include "CoreGraphics/CGGeometry.h"
00026
00029 CPLinearSlider = 0;
00030 CPCircularSlider = 1;
00031
00036 @implementation CPSlider : CPControl
00037 {
00038 double _minValue;
00039 double _maxValue;
00040 double _altIncrementValue;
00041
00042 BOOL _isVertical;
00043 }
00044
00045 + (CPString)themeClass
00046 {
00047 return "slider";
00048 }
00049
00050 + (id)themeAttributes
00051 {
00052 return [CPDictionary dictionaryWithObjects:[nil, _CGSizeMakeZero(), 0.0, nil]
00053 forKeys:[@"knob-color", @"knob-size", @"track-width", @"track-color"]];
00054 }
00055
00056 - (id)initWithFrame:(CGRect)aFrame
00057 {
00058 self = [super initWithFrame:aFrame];
00059
00060 if (self)
00061 {
00062 _minValue = 0.0;
00063 _maxValue = 100.0;
00064
00065 [self setObjectValue:50.0];
00066
00067 [self setContinuous:YES];
00068
00069 [self _recalculateIsVertical];
00070 }
00071
00072 return self;
00073 }
00074
00075 - (void)setMinValue:(float)aMinimumValue
00076 {
00077 if (_minValue === aMinimumValue)
00078 return;
00079
00080 _minValue = aMinimumValue;
00081
00082 var doubleValue = [self doubleValue];
00083
00084 if (doubleValue < _minValue)
00085 [self setDoubleValue:_minValue];
00086 }
00087
00088 - (float)minValue
00089 {
00090 return _minValue;
00091 }
00092
00093 - (void)setMaxValue:(float)aMaximumValue
00094 {
00095 if (_maxValue === aMaximumValue)
00096 return;
00097
00098 _maxValue = aMaximumValue;
00099
00100 var doubleValue = [self doubleValue];
00101
00102 if (doubleValue > _maxValue)
00103 [self setDoubleValue:_maxValue];
00104 }
00105
00106 - (float)maxValue
00107 {
00108 return _maxValue;
00109 }
00110
00111 - (void)setObjectValue:(id)aValue
00112 {
00113 [super setObjectValue:MIN(MAX(aValue, _minValue), _maxValue)];
00114
00115 [self setNeedsLayout];
00116 [self setNeedsDisplay:YES];
00117 }
00118
00119 - (void)setSliderType:(CPSliderType)aSliderType
00120 {
00121 if (aSliderType === CPCircularSlider)
00122 [self setThemeState:CPThemeStateCircular];
00123 else
00124 [self unsetThemeState:CPThemeStateCircular];
00125 }
00126
00127 - (CPSliderType)sliderType
00128 {
00129 return [self hasThemeState:CPThemeStateCircular] ? CPCircularSlider : CPLinearSlider;
00130 }
00131
00132 - (CGRect)trackRectForBounds:(CGRect)bounds
00133 {
00134 if ([self hasThemeState:CPThemeStateCircular])
00135 {
00136 var originalBounds = CGRectCreateCopy(bounds);
00137
00138 bounds.size.width = MIN(bounds.size.width, bounds.size.height);
00139 bounds.size.height = bounds.size.width;
00140
00141 if (bounds.size.width < originalBounds.size.width)
00142 bounds.origin.x += (originalBounds.size.width - bounds.size.width) / 2.0;
00143 else
00144 bounds.origin.y += (originalBounds.size.height - bounds.size.height) / 2.0;
00145 }
00146 else
00147 {
00148 var trackWidth = [self currentValueForThemeAttribute:@"track-width"];
00149
00150 if (trackWidth <= 0)
00151 return _CGRectMakeZero();
00152
00153 if ([self isVertical])
00154 {
00155 bounds.origin.x = (_CGRectGetWidth(bounds) - trackWidth) / 2.0;
00156 bounds.size.width = trackWidth;
00157 }
00158 else
00159 {
00160 bounds.origin.y = (_CGRectGetHeight(bounds) - trackWidth) / 2.0;
00161 bounds.size.height = trackWidth;
00162 }
00163 }
00164
00165 return bounds;
00166 }
00167
00168 - (CGRect)knobRectForBounds:(CGRect)bounds
00169 {
00170 var knobSize = [self currentValueForThemeAttribute:@"knob-size"];
00171
00172 if (knobSize.width <= 0 || knobSize.height <= 0)
00173 return _CGRectMakeZero();
00174
00175 var knobRect = _CGRectMake(0.0, 0.0, knobSize.width, knobSize.height),
00176 trackRect = [self trackRectForBounds:bounds];
00177
00178
00179 if (!trackRect || _CGRectIsEmpty(trackRect))
00180 trackRect = bounds;
00181
00182 if ([self hasThemeState:CPThemeStateCircular])
00183 {
00184 var angle = 3*PI_2 - (1.0 - [self doubleValue] - _minValue) / (_maxValue - _minValue) * PI2,
00185 radius = CGRectGetWidth(trackRect) / 2.0 - 8.0;
00186
00187 knobRect.origin.x = radius * COS(angle) + CGRectGetMidX(trackRect) - 3.0;
00188 knobRect.origin.y = radius * SIN(angle) + CGRectGetMidY(trackRect) - 2.0;
00189 }
00190 else if ([self isVertical])
00191 {
00192 knobRect.origin.x = _CGRectGetMidX(trackRect) - knobSize.width / 2.0;
00193 knobRect.origin.y = (([self doubleValue] - _minValue) / (_maxValue - _minValue)) * (_CGRectGetHeight(trackRect) - knobSize.height);
00194 }
00195 else
00196 {
00197 knobRect.origin.x = (([self doubleValue] - _minValue) / (_maxValue - _minValue)) * (_CGRectGetWidth(trackRect) - knobSize.width);
00198 knobRect.origin.y = _CGRectGetMidY(trackRect) - knobSize.height / 2.0;
00199 }
00200
00201 return knobRect;
00202 }
00203
00204 - (CGRect)rectForEphemeralSubviewNamed:(CPString)aName
00205 {
00206 if (aName === "track-view")
00207 return [self trackRectForBounds:[self bounds]];
00208
00209 else if (aName === "knob-view")
00210 return [self knobRectForBounds:[self bounds]];
00211
00212 return [super rectForEphemeralSubviewNamed:aName];
00213 }
00214
00215 - (CPView)createEphemeralSubviewNamed:(CPString)aName
00216 {
00217 if (aName === "track-view" || aName === "knob-view")
00218 {
00219 var view = [[CPView alloc] init];
00220
00221 [view setHitTests:NO];
00222
00223 return view;
00224 }
00225
00226 return [super createEphemeralSubviewNamed:aName];
00227 }
00228
00229 - (void)setAltIncrementValue:(float)anAltIncrementValue
00230 {
00231 _altIncrementValue = anAltIncrementValue;
00232 }
00233
00234 - (float)altIncrementValue
00235 {
00236 return _altIncrementValue;
00237 }
00238
00239 - (void)setFrameSize:(CGSize)aSize
00240 {
00241 [super setFrameSize:aSize];
00242 [self _recalculateIsVertical];
00243 }
00244
00245 - (void)_recalculateIsVertical
00246 {
00247
00248 var bounds = [self bounds],
00249 width = _CGRectGetWidth(bounds),
00250 height = _CGRectGetHeight(bounds);
00251
00252 _isVertical = width < height ? 1 : (width > height ? 0 : -1);
00253
00254 if (_isVertical === 1)
00255 [self setThemeState:CPThemeStateVertical];
00256 else if (_isVertical === 0)
00257 [self unsetThemeState:CPThemeStateVertical];
00258 }
00259
00260 - (int)isVertical
00261 {
00262 return _isVertical;
00263 }
00264
00265 - (void)layoutSubviews
00266 {
00267 var trackView = [self layoutEphemeralSubviewNamed:@"track-view"
00268 positioned:CPWindowBelow
00269 relativeToEphemeralSubviewNamed:@"knob-view"];
00270
00271 if (trackView)
00272 [trackView setBackgroundColor:[self currentValueForThemeAttribute:@"track-color"]];
00273
00274 var knobView = [self layoutEphemeralSubviewNamed:@"knob-view"
00275 positioned:CPWindowAbove
00276 relativeToEphemeralSubviewNamed:@"track-view"];
00277
00278 if (knobView)
00279 [knobView setBackgroundColor:[self currentValueForThemeAttribute:"knob-color"]];
00280 }
00281
00282 - (BOOL)tracksMouseOutsideOfFrame
00283 {
00284 return YES;
00285 }
00286
00287 - (float)_valueAtPoint:(CGPoint)aPoint
00288 {
00289 var bounds = [self bounds],
00290 knobRect = [self knobRectForBounds:bounds],
00291 trackRect = [self trackRectForBounds:bounds];
00292
00293 if ([self hasThemeState:CPThemeStateCircular])
00294 {
00295 var knobWidth = _CGRectGetWidth(knobRect);
00296
00297 trackRect.origin.x += knobWidth / 2;
00298 trackRect.size.width -= knobWidth;
00299
00300 var minValue = [self minValue],
00301 dx = aPoint.x - _CGRectGetMidX(trackRect),
00302 dy = aPoint.y - _CGRectGetMidY(trackRect);
00303
00304 return MAX(0.0, MIN(1.0, 1.0 - (3 * PI_2 - ATAN2(dy, dx)) % PI2 / PI2)) * ([self maxValue] - minValue) + minValue;
00305 }
00306 else if ([self isVertical])
00307 {
00308 var knobHeight = _CGRectGetHeight(knobRect);
00309
00310 trackRect.origin.y += knobHeight / 2;
00311 trackRect.size.height -= knobHeight;
00312
00313 var minValue = [self minValue];
00314
00315 return MAX(0.0, MIN(1.0, (aPoint.y - _CGRectGetMinY(trackRect)) / _CGRectGetHeight(trackRect))) * ([self maxValue] - minValue) + minValue;
00316 }
00317 else
00318 {
00319 var knobWidth = _CGRectGetWidth(knobRect);
00320
00321 trackRect.origin.x += knobWidth / 2;
00322 trackRect.size.width -= knobWidth;
00323
00324 var minValue = [self minValue];
00325
00326 return MAX(0.0, MIN(1.0, (aPoint.x - _CGRectGetMinX(trackRect)) / _CGRectGetWidth(trackRect))) * ([self maxValue] - minValue) + minValue;
00327 }
00328 }
00329
00330 - (BOOL)startTrackingAt:(CGPoint)aPoint
00331 {
00332 var bounds = [self bounds],
00333 knobRect = [self knobRectForBounds:_CGRectMakeCopy(bounds)];
00334
00335 if (_CGRectContainsPoint(knobRect, aPoint))
00336 _dragOffset = _CGSizeMake(_CGRectGetMidX(knobRect) - aPoint.x, _CGRectGetMidY(knobRect) - aPoint.y);
00337
00338 else
00339 {
00340 var trackRect = [self trackRectForBounds:bounds];
00341
00342 if (trackRect && _CGRectContainsPoint(trackRect, aPoint))
00343 {
00344 _dragOffset = _CGSizeMakeZero();
00345
00346 [self setObjectValue:[self _valueAtPoint:aPoint]];
00347 }
00348
00349 else
00350 return NO;
00351 }
00352
00353 [self setHighlighted:YES];
00354
00355 [self setNeedsLayout];
00356 [self setNeedsDisplay:YES];
00357
00358 return YES;
00359 }
00360
00361 - (BOOL)continueTracking:(CGPoint)lastPoint at:(CGPoint)aPoint
00362 {
00363 [self setObjectValue:[self _valueAtPoint:_CGPointMake(aPoint.x + _dragOffset.width, aPoint.y + _dragOffset.height)]];
00364
00365 return YES;
00366 }
00367
00368 - (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)aPoint mouseIsUp:(BOOL)mouseIsUp
00369 {
00370 [self setHighlighted:NO];
00371
00372 if ([_target respondsToSelector:@selector(sliderDidFinish:)])
00373 [_target sliderDidFinish:self];
00374
00375 [self setNeedsLayout];
00376 [self setNeedsDisplay:YES];
00377 }
00378
00383 - (void)setContinuous:(BOOL)flag
00384 {
00385 if (flag)
00386 _sendActionOn |= CPLeftMouseDraggedMask;
00387 else
00388 _sendActionOn &= ~CPLeftMouseDraggedMask;
00389 }
00390
00391 @end
00392
00393 var CPSliderMinValueKey = "CPSliderMinValueKey",
00394 CPSliderMaxValueKey = "CPSliderMaxValueKey",
00395 CPSliderAltIncrValueKey = "CPSliderAltIncrValueKey";
00396
00397 @implementation CPSlider (CPCoding)
00398
00399 - (id)initWithCoder:(CPCoder)aCoder
00400 {
00401 _minValue = [aCoder decodeDoubleForKey:CPSliderMinValueKey];
00402 _maxValue = [aCoder decodeDoubleForKey:CPSliderMaxValueKey];
00403
00404 self = [super initWithCoder:aCoder];
00405
00406 if (self)
00407 {
00408 _altIncrementValue = [aCoder decodeDoubleForKey:CPSliderAltIncrValueKey];
00409
00410 [self setContinuous:YES];
00411
00412 [self _recalculateIsVertical];
00413
00414 [self setNeedsLayout];
00415 [self setNeedsDisplay:YES];
00416 }
00417
00418 return self;
00419 }
00420
00421 - (void)encodeWithCoder:(CPCoder)aCoder
00422 {
00423 [super encodeWithCoder:aCoder];
00424
00425 [aCoder encodeDouble:_minValue forKey:CPSliderMinValueKey];
00426 [aCoder encodeDouble:_maxValue forKey:CPSliderMaxValueKey];
00427 [aCoder encodeDouble:_altIncrementValue forKey:CPSliderAltIncrValueKey];
00428 }
00429
00430 @end
00431
00432 @implementation CPSlider (Deprecated)
00433
00434 - (id)value
00435 {
00436 CPLog.warn("[CPSlider value] is deprecated, use doubleValue or objectValue instead.");
00437
00438 return [self doubleValue];
00439 }
00440
00441 - (void)setValue:(id)aValue
00442 {
00443 CPLog.warn("[CPSlider setValue:] is deprecated, use setDoubleValue: or setObjectValue: instead.");
00444
00445 [self setObjectValue:aValue];
00446 }
00447
00448 @end