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