26 #include "../Foundation/Foundation.h"
56 var _CACHED_THEME_SCROLLER = nil;
77 CPControlSize _controlSize;
78 CPUsableScrollerParts _usableParts;
82 float _knobProportion;
84 CPScrollerPart _hitPart;
86 CPScrollerPart _trackingPart;
87 float _trackingFloatValue;
88 CGPoint _trackingStartPoint;
100 #pragma mark Class methods
110 @"scroller-width": 7.0,
115 @"decrement-line-size": CGSizeMakeZero(),
116 @"increment-line-size": CGSizeMakeZero(),
117 @"track-inset": CGInsetMakeZero(),
118 @"knob-inset": CGInsetMakeZero(),
119 @"minimum-knob-length": 21.0,
120 @"track-border-overlay": 9.0
124 + (float)scrollerWidth
132 + (float)scrollerWidthInStyle:(
int)aStyle
134 if (!_CACHED_THEME_SCROLLER)
135 _CACHED_THEME_SCROLLER = [[
self alloc] init];
138 return [_CACHED_THEME_SCROLLER valueForThemeAttribute:@"scroller-width" inState:CPThemeStateScrollViewLegacy];
140 return [_CACHED_THEME_SCROLLER currentValueForThemeAttribute:@"scroller-width"];
146 + (float)scrollerOverlay
148 if (!_CACHED_THEME_SCROLLER)
149 _CACHED_THEME_SCROLLER = [[
self alloc] init];
151 return [_CACHED_THEME_SCROLLER currentValueForThemeAttribute:@"track-border-overlay"];
158 + (float)scrollerWidthForControlSize:(CPControlSize)aControlSize
165 #pragma mark Initialization
167 - (id)initWithFrame:(CGRect)aFrame
169 if (
self = [super initWithFrame:aFrame])
178 _allowFadingOut = YES;
182 var paramAnimFadeOut = @{
183 CPViewAnimationTargetKey:
self,
184 CPViewAnimationEffectKey: CPViewAnimationFadeOutEffect,
187 _animationScroller = [[
CPViewAnimation alloc] initWithDuration:0.2 animationCurve:CPAnimationEaseInOut];
188 [_animationScroller setViewAnimations:[paramAnimFadeOut]];
189 [_animationScroller setDelegate:self];
190 [
self setAlphaValue:0.0];
194 [
self _setIsVertical:CGRectGetHeight(aFrame) >= CGRectGetWidth(aFrame)];
202 #pragma mark Getters / Setters
216 - (void)setStyle:(
id)aStyle
218 if (_style != nil && _style === aStyle)
226 [
self setThemeState:CPThemeStateScrollViewLegacy];
230 _allowFadingOut = YES;
231 [
self unsetThemeState:CPThemeStateScrollViewLegacy];
237 - (void)setObjectValue:(
id)aValue
245 - (CPControlSize)controlSize
254 - (void)setControlSize:(CPControlSize)aControlSize
256 if (_controlSize == aControlSize)
259 _controlSize = aControlSize;
261 [
self setNeedsLayout];
262 [
self setNeedsDisplay:YES];
268 - (float)knobProportion
270 return _knobProportion;
277 - (void)setKnobProportion:(
float)aProportion
279 if (!_IS_NUMERIC(aProportion))
282 _knobProportion = MIN(1.0, MAX(0.0001, aProportion));
284 [
self setNeedsDisplay:YES];
285 [
self setNeedsLayout];
290 #pragma mark Privates
293 - (void)_adjustScrollerSize
295 var
frame = [
self frame],
296 scrollerWidth = [
self currentValueForThemeAttribute:@"scroller-width"];
298 if ([
self isVertical] && CGRectGetWidth(
frame) !== scrollerWidth)
299 frame.size.width = scrollerWidth;
301 if (![
self isVertical] && CGRectGetHeight(
frame) !== scrollerWidth)
302 frame.size.height = scrollerWidth;
304 [
self setFrame:frame];
308 - (void)_performFadeOut:(
CPTimer)aTimer
316 #pragma mark Utilities
318 - (CGRect)rectForPart:(CPScrollerPart)aPart
321 return CGRectMakeZero();
323 return _partRects[aPart];
331 - (CPScrollerPart)testPart:(CGPoint)aPoint
333 aPoint = [
self convertPoint:aPoint fromView:nil];
338 if (![
self hasThemeState:CPThemeStateSelected])
341 if (CGRectContainsPoint([
self rectForPart:
CPScrollerKnob], aPoint))
365 - (void)checkSpaceForParts
367 var bounds = [
self bounds];
370 if (_knobProportion === 1.0)
374 _partRects[CPScrollerDecrementPage] = CGRectMakeZero();
375 _partRects[CPScrollerKnob] = CGRectMakeZero();
376 _partRects[CPScrollerIncrementPage] = CGRectMakeZero();
377 _partRects[CPScrollerDecrementLine] = CGRectMakeZero();
378 _partRects[CPScrollerIncrementLine] = CGRectMakeZero();
381 _partRects[CPScrollerKnobSlot] = CGRectMakeCopy(bounds);
391 width = CGRectGetWidth(bounds),
392 height = CGRectGetHeight(bounds);
394 if ([
self isVertical])
398 effectiveDecrementLineHeight = decrementLineSize.height + trackInset.top,
399 effectiveIncrementLineHeight = incrementLineSize.height + trackInset.bottom,
400 slotSize = height - effectiveDecrementLineHeight - effectiveIncrementLineHeight,
402 knobWidth =
width - knobInset.left - knobInset.right,
403 knobHeight = MAX(minimumKnobLength, (slotSize * _knobProportion)),
404 knobLocation = effectiveDecrementLineHeight + (slotSize - knobHeight) * [
self floatValue];
406 _partRects[CPScrollerDecrementPage] = CGRectMake(0.0, effectiveDecrementLineHeight,
width, knobLocation - effectiveDecrementLineHeight);
407 _partRects[CPScrollerKnob] = CGRectMake(knobInset.left, knobLocation, knobWidth, knobHeight);
408 _partRects[CPScrollerIncrementPage] = CGRectMake(0.0, knobLocation + knobHeight,
width, height - (knobLocation + knobHeight) - effectiveIncrementLineHeight);
409 _partRects[CPScrollerKnobSlot] = CGRectMake(trackInset.left, effectiveDecrementLineHeight,
width - trackInset.left - trackInset.right, slotSize);
410 _partRects[CPScrollerDecrementLine] = CGRectMake(0.0, 0.0, decrementLineSize.width, decrementLineSize.height);
411 _partRects[CPScrollerIncrementLine] = CGRectMake(0.0, height - incrementLineSize.height, incrementLineSize.width, incrementLineSize.height);
413 if (height < knobHeight + decrementLineSize.height + incrementLineSize.height + trackInset.top + trackInset.bottom)
414 _partRects[CPScrollerKnob] = CGRectMakeZero();
416 if (height < decrementLineSize.height + incrementLineSize.height - 2)
418 _partRects[CPScrollerIncrementLine] = CGRectMakeZero();
419 _partRects[CPScrollerDecrementLine] = CGRectMakeZero();
420 _partRects[CPScrollerKnobSlot] = CGRectMake(trackInset.left, 0,
width - trackInset.left - trackInset.right, height);
427 effectiveDecrementLineWidth = decrementLineSize.width + trackInset.left,
428 effectiveIncrementLineWidth = incrementLineSize.width + trackInset.right,
429 slotSize =
width - effectiveDecrementLineWidth - effectiveIncrementLineWidth,
431 knobWidth = MAX(minimumKnobLength, (slotSize * _knobProportion)),
432 knobHeight = height - knobInset.top - knobInset.bottom,
433 knobLocation = effectiveDecrementLineWidth + (slotSize - knobWidth) * [
self floatValue];
435 _partRects[CPScrollerDecrementPage] = CGRectMake(effectiveDecrementLineWidth, 0.0, knobLocation - effectiveDecrementLineWidth, height);
436 _partRects[CPScrollerKnob] = CGRectMake(knobLocation, knobInset.top, knobWidth, knobHeight);
437 _partRects[CPScrollerIncrementPage] = CGRectMake(knobLocation + knobWidth, 0.0,
width - (knobLocation + knobWidth) - effectiveIncrementLineWidth, height);
438 _partRects[CPScrollerKnobSlot] = CGRectMake(effectiveDecrementLineWidth, trackInset.top, slotSize, height - trackInset.top - trackInset.bottom);
439 _partRects[CPScrollerDecrementLine] = CGRectMake(0.0, 0.0, decrementLineSize.width, decrementLineSize.height);
440 _partRects[CPScrollerIncrementLine] = CGRectMake(
width - incrementLineSize.width, 0.0, incrementLineSize.width, incrementLineSize.height);
442 if (
width < knobWidth + decrementLineSize.width + incrementLineSize.width + trackInset.left + trackInset.right)
443 _partRects[CPScrollerKnob] = CGRectMakeZero();
445 if (
width < decrementLineSize.width + incrementLineSize.width - 2)
447 _partRects[CPScrollerIncrementLine] = CGRectMakeZero();
448 _partRects[CPScrollerDecrementLine] = CGRectMakeZero();
449 _partRects[CPScrollerKnobSlot] = CGRectMake(0.0, 0.0,
width, slotSize);
458 - (CPUsableScrollerParts)usableParts
468 if (_isMouseOver && _knobProportion != 1.0)
469 [
self setThemeState:CPThemeStateSelected];
472 [_timerFadeOut invalidate];
474 [
self setAlphaValue:1.0];
485 [_animationScroller startAnimation];
497 - (void)drawArrow:(CPScrollerArrow)anArrow highlight:(BOOL)shouldHighlight
515 - (
CPView)createViewForPart:(CPScrollerPart)aPart
517 var view = [[
CPView alloc] initWithFrame:CGRectMakeZero()];
519 [view setHitTests:NO];
524 - (CGRect)rectForEphemeralSubviewNamed:(
CPString)aName
526 return _partRects[aName];
531 var view = [[
CPView alloc] initWithFrame:CGRectMakeZero()];
533 [view setHitTests:NO];
538 - (void)layoutSubviews
540 [
self _adjustScrollerSize];
547 for (; index < count; ++index)
552 view = [
self layoutEphemeralSubviewNamed:part positioned:CPWindowBelow relativeToEphemeralSubviewNamed:PARTS_ARRANGEMENT[index + 1]];
554 view = [
self layoutEphemeralSubviewNamed:part positioned:CPWindowAbove relativeToEphemeralSubviewNamed:PARTS_ARRANGEMENT[index - 1]];
576 - (CPScrollerPart)hitPart
587 var type = [anEvent type];
599 _trackingStartPoint = [
self convertPoint:[anEvent locationInWindow] fromView:nil];
605 knobSlotRect = [
self rectForPart:CPScrollerKnobSlot],
606 remainder = ![
self isVertical] ? (CGRectGetWidth(knobSlotRect) - CGRectGetWidth(knobRect)) : (CGRectGetHeight(knobSlotRect) - CGRectGetHeight(knobRect));
612 var location = [
self convertPoint:[anEvent locationInWindow] fromView:nil],
613 delta = ![
self isVertical] ? location.x - _trackingStartPoint.x : location.y - _trackingStartPoint.y;
615 [
self setFloatValue:_trackingFloatValue + delta / remainder];
619 [CPApp setTarget:self selector:@selector(trackKnob:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
631 var type = [anEvent type];
645 _trackingPart = [
self hitPart];
647 _trackingStartPoint = [
self convertPoint:[anEvent locationInWindow] fromView:nil];
660 knobWidth = ![
self isVertical] ? CGRectGetWidth(knobRect) : CGRectGetHeight(knobRect),
661 knobSlotRect = [
self rectForPart:CPScrollerKnobSlot],
662 remainder = (![
self isVertical] ? CGRectGetWidth(knobSlotRect) : CGRectGetHeight(knobSlotRect)) - knobWidth;
664 [
self setFloatValue:((![
self isVertical] ? _trackingStartPoint.x - CGRectGetMinX(knobSlotRect) : _trackingStartPoint.y - CGRectGetMinY(knobSlotRect)) - knobWidth / 2.0) / remainder];
678 [
CPEvent startPeriodicEventsAfterDelay:0.5 withPeriod:0.04];
683 _trackingStartPoint = [
self convertPoint:[anEvent locationInWindow] fromView:nil];
687 var hitPart = [
self testPart:[anEvent locationInWindow]];
691 _trackingPart = hitPart;
698 else if (type ==
CPPeriodic && CGRectContainsPoint([
self rectForPart:_trackingPart], _trackingStartPoint))
699 [
self sendAction:[
self action] to:[
self target]];
701 [CPApp setTarget:self selector:@selector(trackScrollButtons:) forNextEventMatchingMask:CPPeriodicMask | CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
705 - (void)_setIsVertical:(BOOL)isVertical
707 _isVertical = isVertical;
710 [
self setThemeState:CPThemeStateVertical];
712 [
self unsetThemeState:CPThemeStateVertical];
715 - (void)setFrameSize:(CGSize)aSize
717 [
super setFrameSize:aSize];
720 [
self setNeedsLayout];
725 #pragma mark Overrides
727 - (id)currentValueForThemeAttribute:(
CPString)anAttributeName
729 var themeState = _themeState;
732 themeState &= ~CPThemeStateHighlighted;
734 return [
self valueForThemeAttribute:anAttributeName inState:themeState];
739 if (![
self isEnabled])
742 _hitPart = [
self testPart:[anEvent locationInWindow]];
762 [_timerFadeOut invalidate];
764 if (![
self isEnabled])
767 _allowFadingOut = NO;
770 if ([
self alphaValue] > 0 && _knobProportion != 1.0)
771 [
self setThemeState:CPThemeStateSelected];
778 if ([
self isHidden] || ![
self isEnabled] || !_isMouseOver)
781 _allowFadingOut = YES;
785 [_timerFadeOut invalidate];
792 #pragma mark Delegates
796 [
self unsetThemeState:CPThemeStateSelected];
810 if (
self = [super initWithCoder:aCoder])
814 _controlSize = [aCoder decodeIntForKey:CPScrollerControlSizeKey];
816 _knobProportion = 1.0;
819 _knobProportion = [aCoder decodeFloatForKey:CPScrollerKnobProportionKey];
825 _allowFadingOut = YES;
828 var paramAnimFadeOut = @{
829 CPViewAnimationTargetKey:
self,
830 CPViewAnimationEffectKey: CPViewAnimationFadeOutEffect,
833 _animationScroller = [[
CPViewAnimation alloc] initWithDuration:0.2 animationCurve:CPAnimationEaseInOut];
834 [_animationScroller setViewAnimations:[paramAnimFadeOut]];
835 [_animationScroller setDelegate:self];
836 [
self setAlphaValue:0.0];
838 [
self setStyle:[aCoder decodeIntForKey:CPScrollerStyleKey]];
840 [
self _setIsVertical:[aCoder decodeBoolForKey:CPScrollerIsVerticalKey]];
850 [aCoder encodeInt:_controlSize forKey:CPScrollerControlSizeKey];
851 [aCoder encodeInt:_isVertical forKey:CPScrollerIsVerticalKey];
852 [aCoder encodeFloat:_knobProportion forKey:CPScrollerKnobProportionKey];
853 [aCoder encodeInt:_style forKey:CPScrollerStyleKey];
865 - (void)setFloatValue:(
float)aValue knobProportion:(
float)aProportion
886 - (BOOL)allowFadingOut
888 return _allowFadingOut;