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
00027
00028
00029 CPScrollerNoPart = 0;
00030 CPScrollerDecrementPage = 1;
00031 CPScrollerKnob = 2;
00032 CPScrollerIncrementPage = 3;
00033 CPScrollerDecrementLine = 4;
00034 CPScrollerIncrementLine = 5;
00035 CPScrollerKnobSlot = 6;
00036
00037 CPScrollerIncrementArrow = 0;
00038 CPScrollerDecrementArrow = 1;
00039
00040 CPNoScrollerParts = 0;
00041 CPOnlyScrollerArrows = 1;
00042 CPAllScrollerParts = 2;
00043
00049 var PARTS_ARRANGEMENT = [CPScrollerKnobSlot, CPScrollerDecrementLine, CPScrollerIncrementLine, CPScrollerKnob],
00050 NAMES_FOR_PARTS = {},
00051 PARTS_FOR_NAMES = {};
00052
00053 NAMES_FOR_PARTS[CPScrollerDecrementLine] = @"decrement-line";
00054 NAMES_FOR_PARTS[CPScrollerIncrementLine] = @"increment-line";
00055 NAMES_FOR_PARTS[CPScrollerKnobSlot] = @"knob-slot";
00056 NAMES_FOR_PARTS[CPScrollerKnob] = @"knob";
00057
00058
00059 @implementation CPScroller : CPControl
00060 {
00061 CPControlSize _controlSize;
00062 CPUsableScrollerParts _usableParts;
00063 CPArray _partRects;
00064
00065 BOOL _isVertical @accessors(readonly, getter=isVertical);
00066 float _knobProportion;
00067
00068 CPScrollerPart _hitPart;
00069
00070 CPScrollerPart _trackingPart;
00071 float _trackingFloatValue;
00072 CGPoint _trackingStartPoint;
00073 }
00074
00075 + (CPString)themeClass
00076 {
00077 return "scroller";
00078 }
00079
00080 + (id)themeAttributes
00081 {
00082 return [CPDictionary dictionaryWithObjects:[ nil, nil, nil, nil,
00083 _CGSizeMakeZero(), _CGSizeMakeZero(), _CGInsetMakeZero(), _CGInsetMakeZero(), _CGSizeMakeZero()]
00084 forKeys:[ @"knob-slot-color",
00085 @"decrement-line-color",
00086 @"increment-line-color",
00087 @"knob-color",
00088 @"decrement-line-size",
00089 @"increment-line-size",
00090 @"track-inset",
00091 @"knob-inset",
00092 @"minimum-knob-length"]];
00093 }
00094
00095
00096
00097
00098 - (id)initWithFrame:(CGRect)aFrame
00099 {
00100 self = [super initWithFrame:aFrame];
00101
00102 if (self)
00103 {
00104 _controlSize = CPRegularControlSize;
00105 _partRects = [];
00106
00107 [self setFloatValue:0.0 knobProportion:1.0];
00108
00109 _hitPart = CPScrollerNoPart;
00110
00111 [self _recalculateIsVertical];
00112 }
00113
00114 return self;
00115 }
00116
00117
00121 + (float)scrollerWidth
00122 {
00123 return 17.0;
00124 }
00125
00130 + (float)scrollerWidthForControlSize:(CPControlSize)aControlSize
00131 {
00132 return 17.0;
00133 }
00134
00139 - (void)setControlSize:(CPControlSize)aControlSize
00140 {
00141 if (_controlSize == aControlSize)
00142 return;
00143
00144 _controlSize = aControlSize;
00145
00146 [self setNeedsLayout];
00147 [self setNeedsDisplay:YES];
00148 }
00149
00153 - (CPControlSize)controlSize
00154 {
00155 return _controlSize;
00156 }
00157
00158
00163 - (void)setFloatValue:(float)aValue
00164 {
00165 [super setFloatValue:MIN(1.0, MAX(0.0, aValue))];
00166
00167 [self setNeedsLayout];
00168 }
00169
00175 - (void)setFloatValue:(float)aValue knobProportion:(float)aProportion
00176 {
00177 _knobProportion = MIN(1.0, MAX(0.0001, aProportion));
00178
00179 [self setFloatValue:aValue];
00180 }
00181
00185 - (float)knobProportion
00186 {
00187 return _knobProportion;
00188 }
00189
00190 - (id)currentValueForThemeAttribute:(CPString)anAttributeName
00191 {
00192 var themeState = _themeState;
00193
00194 if (NAMES_FOR_PARTS[_hitPart] + "-color" !== anAttributeName)
00195 themeState &= ~CPThemeStateHighlighted;
00196
00197 return [self valueForThemeAttribute:anAttributeName inState:themeState];
00198 }
00199
00200
00201
00202 - (CGRect)rectForPart:(CPScrollerPart)aPart
00203 {
00204 if (aPart == CPScrollerNoPart)
00205 return _CGRectMakeZero();
00206
00207 return _partRects[aPart];
00208 }
00209
00215 - (CPScrollerPart)testPart:(CGPoint)aPoint
00216 {
00217 aPoint = [self convertPoint:aPoint fromView:nil];
00218
00219
00220
00221
00222 if (CGRectContainsPoint([self rectForPart:CPScrollerKnob], aPoint))
00223 return CPScrollerKnob;
00224
00225 if (CGRectContainsPoint([self rectForPart:CPScrollerDecrementPage], aPoint))
00226 return CPScrollerDecrementPage;
00227
00228 if (CGRectContainsPoint([self rectForPart:CPScrollerIncrementPage], aPoint))
00229 return CPScrollerIncrementPage;
00230
00231 if (CGRectContainsPoint([self rectForPart:CPScrollerDecrementLine], aPoint))
00232 return CPScrollerDecrementLine;
00233
00234 if (CGRectContainsPoint([self rectForPart:CPScrollerIncrementLine], aPoint))
00235 return CPScrollerIncrementLine;
00236
00237 if (CGRectContainsPoint([self rectForPart:CPScrollerKnobSlot], aPoint))
00238 return CPScrollerKnobSlot;
00239
00240 return CPScrollerNoPart;
00241 }
00242
00246 - (void)checkSpaceForParts
00247 {
00248 var bounds = [self bounds];
00249
00250
00251 if (_knobProportion === 1.0)
00252 {
00253 _usableParts = CPNoScrollerParts;
00254
00255 _partRects[CPScrollerDecrementPage] = _CGRectMakeZero();
00256 _partRects[CPScrollerKnob] = _CGRectMakeZero();
00257 _partRects[CPScrollerIncrementPage] = _CGRectMakeZero();
00258 _partRects[CPScrollerDecrementLine] = _CGRectMakeZero();
00259 _partRects[CPScrollerIncrementLine] = _CGRectMakeZero();
00260
00261
00262 _partRects[CPScrollerKnobSlot] = _CGRectMakeCopy(bounds);
00263
00264 return;
00265 }
00266
00267
00268 _usableParts = CPAllScrollerParts;
00269
00270 var knobInset = [self currentValueForThemeAttribute:@"knob-inset"],
00271 trackInset = [self currentValueForThemeAttribute:@"track-inset"],
00272 width = _CGRectGetWidth(bounds),
00273 height = _CGRectGetHeight(bounds);
00274
00275 if ([self isVertical])
00276 {
00277 var decrementLineSize = [self currentValueForThemeAttribute:"decrement-line-size"],
00278 incrementLineSize = [self currentValueForThemeAttribute:"increment-line-size"],
00279 effectiveDecrementLineHeight = decrementLineSize.height + trackInset.top,
00280 effectiveIncrementLineHeight = incrementLineSize.height + trackInset.bottom,
00281 slotHeight = height - effectiveDecrementLineHeight - effectiveIncrementLineHeight,
00282 minimumKnobLength = [self currentValueForThemeAttribute:"minimum-knob-length"],
00283 knobWidth = width - knobInset.left - knobInset.right,
00284 knobHeight = MAX(minimumKnobLength, (slotHeight * _knobProportion)),
00285 knobLocation = effectiveDecrementLineHeight + (slotHeight - knobHeight) * [self floatValue];
00286
00287 _partRects[CPScrollerDecrementPage] = _CGRectMake(0.0, effectiveDecrementLineHeight, width, knobLocation - effectiveDecrementLineHeight);
00288 _partRects[CPScrollerKnob] = _CGRectMake(knobInset.left, knobLocation, knobWidth, knobHeight);
00289 _partRects[CPScrollerIncrementPage] = _CGRectMake(0.0, knobLocation + knobHeight, width, height - (knobLocation + knobHeight) - effectiveIncrementLineHeight);
00290 _partRects[CPScrollerKnobSlot] = _CGRectMake(trackInset.left, effectiveDecrementLineHeight, width - trackInset.left - trackInset.right, slotHeight);
00291 _partRects[CPScrollerDecrementLine] = _CGRectMake(0.0, 0.0, decrementLineSize.width, decrementLineSize.height);
00292 _partRects[CPScrollerIncrementLine] = _CGRectMake(0.0, height - incrementLineSize.height, incrementLineSize.width, incrementLineSize.height);
00293 }
00294
00295 else
00296 {
00297 var decrementLineSize = [self currentValueForThemeAttribute:"decrement-line-size"],
00298 incrementLineSize = [self currentValueForThemeAttribute:"increment-line-size"],
00299 effectiveDecrementLineWidth = decrementLineSize.width + trackInset.left,
00300 effectiveIncrementLineWidth = incrementLineSize.width + trackInset.right;
00301 slotWidth = width - effectiveDecrementLineWidth - effectiveIncrementLineWidth,
00302 minimumKnobLength = [self currentValueForThemeAttribute:"minimum-knob-length"],
00303 knobWidth = MAX(minimumKnobLength, (slotWidth * _knobProportion)),
00304 knobHeight = height - knobInset.top - knobInset.bottom,
00305 knobLocation = effectiveDecrementLineWidth + (slotWidth - knobWidth) * [self floatValue];
00306
00307 _partRects[CPScrollerDecrementPage] = _CGRectMake(effectiveDecrementLineWidth, 0.0, knobLocation - effectiveDecrementLineWidth, height);
00308 _partRects[CPScrollerKnob] = _CGRectMake(knobLocation, knobInset.top, knobWidth, knobHeight);
00309 _partRects[CPScrollerIncrementPage] = _CGRectMake(knobLocation + knobWidth, 0.0, width - (knobLocation + knobWidth) - effectiveIncrementLineWidth, height);
00310 _partRects[CPScrollerKnobSlot] = _CGRectMake(effectiveDecrementLineWidth, trackInset.top, slotWidth, height - trackInset.top - trackInset.bottom);
00311 _partRects[CPScrollerDecrementLine] = _CGRectMake(0.0, 0.0, decrementLineSize.width, decrementLineSize.height);
00312 _partRects[CPScrollerIncrementLine] = _CGRectMake(width - incrementLineSize.width, 0.0, incrementLineSize.width, incrementLineSize.height);
00313 }
00314 }
00315
00320 - (CPUsableScrollerParts)usableParts
00321 {
00322 return _usableParts;
00323 }
00324
00325
00331 - (void)drawArrow:(CPScrollerArrow)anArrow highlight:(BOOL)shouldHighlight
00332 {
00333 }
00334
00338 - (void)drawKnob
00339 {
00340 }
00341
00345 - (void)drawKnobSlot
00346 {
00347 }
00348
00349 - (CPView)createViewForPart:(CPScrollerPart)aPart
00350 {
00351 var view = [[CPView alloc] initWithFrame:_CGRectMakeZero()];
00352
00353 [view setHitTests:NO];
00354
00355 return view;
00356 }
00357
00358 - (CGRect)rectForEphemeralSubviewNamed:(CPString)aName
00359 {
00360 return _partRects[aName];
00361 }
00362
00363 - (CPView)createEphemeralSubviewNamed:(CPString)aName
00364 {
00365 var view = [[CPView alloc] initWithFrame:_CGRectMakeZero()];
00366
00367 [view setHitTests:NO];
00368
00369 return view;
00370 }
00371
00372 - (void)layoutSubviews
00373 {
00374 [self checkSpaceForParts];
00375
00376 var index = 0,
00377 count = PARTS_ARRANGEMENT.length;
00378
00379 for (; index < count; ++index)
00380 {
00381 var part = PARTS_ARRANGEMENT[index];
00382
00383 if (index === 0)
00384 view = [self layoutEphemeralSubviewNamed:part positioned:CPWindowBelow relativeToEphemeralSubviewNamed:PARTS_ARRANGEMENT[index + 1]];
00385 else
00386 view = [self layoutEphemeralSubviewNamed:part positioned:CPWindowAbove relativeToEphemeralSubviewNamed:PARTS_ARRANGEMENT[index - 1]];
00387
00388 if (view)
00389 [view setBackgroundColor:[self currentValueForThemeAttribute:NAMES_FOR_PARTS[part] + "-color"]];
00390 }
00391 }
00392
00396 - (void)drawParts
00397 {
00398 [self drawKnobSlot];
00399 [self drawKnob];
00400 [self drawArrow:CPScrollerDecrementArrow highlight:NO];
00401 [self drawArrow:CPScrollerIncrementArrow highlight:NO];
00402 }
00403
00404
00408 - (CPScrollerPart)hitPart
00409 {
00410 return _hitPart;
00411 }
00412
00417 - (void)trackKnob:(CPEvent)anEvent
00418 {
00419 var type = [anEvent type];
00420
00421 if (type === CPLeftMouseUp)
00422 {
00423 _hitPart = CPScrollerNoPart;
00424
00425 return;
00426 }
00427
00428 if (type === CPLeftMouseDown)
00429 {
00430 _trackingFloatValue = [self floatValue];
00431 _trackingStartPoint = [self convertPoint:[anEvent locationInWindow] fromView:nil];
00432 }
00433
00434 else if (type === CPLeftMouseDragged)
00435 {
00436 var knobRect = [self rectForPart:CPScrollerKnob],
00437 knobSlotRect = [self rectForPart:CPScrollerKnobSlot],
00438 remainder = ![self isVertical] ? (_CGRectGetWidth(knobSlotRect) - _CGRectGetWidth(knobRect)) : (_CGRectGetHeight(knobSlotRect) - _CGRectGetHeight(knobRect));
00439
00440 if (remainder <= 0)
00441 [self setFloatValue:0.0];
00442 else
00443 {
00444 var location = [self convertPoint:[anEvent locationInWindow] fromView:nil];
00445 delta = ![self isVertical] ? location.x - _trackingStartPoint.x : location.y - _trackingStartPoint.y;
00446
00447 [self setFloatValue:_trackingFloatValue + delta / remainder];
00448 }
00449 }
00450
00451 [CPApp setTarget:self selector:@selector(trackKnob:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
00452
00453 [self sendAction:[self action] to:[self target]];
00454 }
00455
00460 - (void)trackScrollButtons:(CPEvent)anEvent
00461 {
00462 var type = [anEvent type];
00463
00464 if (type === CPLeftMouseUp)
00465 {
00466 [self highlight:NO];
00467 [CPEvent stopPeriodicEvents];
00468
00469 _hitPart = CPScrollerNoPart;
00470
00471 return;
00472 }
00473
00474 if (type === CPLeftMouseDown)
00475 {
00476 _trackingPart = [self hitPart];
00477
00478 _trackingStartPoint = [self convertPoint:[anEvent locationInWindow] fromView:nil];
00479
00480 if ([anEvent modifierFlags] & CPAlternateKeyMask)
00481 {
00482 if (_trackingPart == CPScrollerDecrementLine)
00483 _hitPart = CPScrollerDecrementPage;
00484
00485 else if (_trackingPart == CPScrollerIncrementLine)
00486 _hitPart = CPScrollerIncrementPage;
00487
00488 else if (_trackingPart == CPScrollerDecrementPage || _trackingPart == CPScrollerIncrementPage)
00489 {
00490 var knobRect = [self rectForPart:CPScrollerKnob],
00491 knobWidth = ![self isVertical] ? _CGRectGetWidth(knobRect) : _CGRectGetHeight(knobRect),
00492 knobSlotRect = [self rectForPart:CPScrollerKnobSlot],
00493 remainder = (![self isVertical] ? _CGRectGetWidth(knobSlotRect) : _CGRectGetHeight(knobSlotRect)) - knobWidth;
00494
00495 [self setFloatValue:((![self isVertical] ? _trackingStartPoint.x - _CGRectGetMinX(knobSlotRect) : _trackingStartPoint.y - _CGRectGetMinY(knobSlotRect)) - knobWidth / 2.0) / remainder];
00496
00497 _hitPart = CPScrollerKnob;
00498
00499 [self sendAction:[self action] to:[self target]];
00500
00501
00502 return [self trackKnob:anEvent];
00503 }
00504 }
00505
00506 [self highlight:YES];
00507 [self sendAction:[self action] to:[self target]];
00508
00509 [CPEvent startPeriodicEventsAfterDelay:0.5 withPeriod:0.04];
00510 }
00511
00512 else if (type === CPLeftMouseDragged)
00513 {
00514 _trackingStartPoint = [self convertPoint:[anEvent locationInWindow] fromView:nil];
00515
00516 if (_trackingPart == CPScrollerDecrementPage || _trackingPart == CPScrollerIncrementPage)
00517 {
00518 var hitPart = [self testPart:[anEvent locationInWindow]];
00519
00520 if (hitPart == CPScrollerDecrementPage || hitPart == CPScrollerIncrementPage)
00521 {
00522 _trackingPart = hitPart;
00523 _hitPart = hitPart;
00524 }
00525 }
00526
00527 [self highlight:CGRectContainsPoint([self rectForPart:_trackingPart], _trackingStartPoint)];
00528 }
00529 else if (type == CPPeriodic && CGRectContainsPoint([self rectForPart:_trackingPart], _trackingStartPoint))
00530 [self sendAction:[self action] to:[self target]];
00531
00532 [CPApp setTarget:self selector:@selector(trackScrollButtons:) forNextEventMatchingMask:CPPeriodicMask | CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
00533
00534 }
00535
00536 - (void)_recalculateIsVertical
00537 {
00538
00539 var bounds = [self bounds],
00540 width = _CGRectGetWidth(bounds),
00541 height = _CGRectGetHeight(bounds);
00542
00543 _isVertical = width < height ? 1 : (width > height ? 0 : -1);
00544
00545 if (_isVertical === 1)
00546 [self setThemeState:CPThemeStateVertical];
00547 else if (_isVertical === 0)
00548 [self unsetThemeState:CPThemeStateVertical];
00549 }
00550
00551 - (void)setFrameSize:(CGSize)aSize
00552 {
00553 [super setFrameSize:aSize];
00554
00555 [self _recalculateIsVertical];
00556
00557 [self checkSpaceForParts];
00558 [self setNeedsLayout];
00559 }
00560
00561 - (void)mouseDown:(CPEvent)anEvent
00562 {
00563 if (![self isEnabled])
00564 return;
00565
00566 _hitPart = [self testPart:[anEvent locationInWindow]];
00567
00568 switch (_hitPart)
00569 {
00570 case CPScrollerKnob: return [self trackKnob:anEvent];
00571
00572 case CPScrollerDecrementLine:
00573 case CPScrollerIncrementLine:
00574 case CPScrollerDecrementPage:
00575 case CPScrollerIncrementPage: return [self trackScrollButtons:anEvent];
00576 }
00577 }
00578
00579 @end
00580
00581 var CPScrollerControlSizeKey = "CPScrollerControlSize",
00582 CPScrollerKnobProportionKey = "CPScrollerKnobProportion";
00583
00584 @implementation CPScroller (CPCoding)
00585
00586 - (id)initWithCoder:(CPCoder)aCoder
00587 {
00588 if (self = [super initWithCoder:aCoder])
00589 {
00590 _controlSize = CPRegularControlSize;
00591 if ([aCoder containsValueForKey:CPScrollerControlSizeKey])
00592 _controlSize = [aCoder decodeIntForKey:CPScrollerControlSizeKey];
00593
00594 _knobProportion = 1.0;
00595 if ([aCoder containsValueForKey:CPScrollerKnobProportionKey])
00596 _knobProportion = [aCoder decodeFloatForKey:CPScrollerKnobProportionKey];
00597
00598 _partRects = [];
00599
00600 _hitPart = CPScrollerNoPart;
00601
00602 [self _recalculateIsVertical];
00603
00604
00605 }
00606
00607 return self;
00608 }
00609
00610 - (void)encodeWithCoder:(CPCoder)aCoder
00611 {
00612 [super encodeWithCoder:aCoder];
00613
00614 [aCoder encodeInt:_controlSize forKey:CPScrollerControlSizeKey];
00615 [aCoder encodeFloat:_knobProportion forKey:CPScrollerKnobProportionKey];
00616 }
00617
00618 @end