API  0.9.7
 All Classes Files Functions Variables Macros Groups Pages
CPScroller.j
Go to the documentation of this file.
1 /*
2  * CPScroller.j
3  * AppKit
4  *
5  * Created by Francisco Tolmasky.
6  * Copyright 2008, 280 North, Inc.
7  *
8  * Modified to match Lion style by Antoine Mercadal 2011
9  * <antoine.mercadal@archipelproject.org>
10  *
11  * This library is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Lesser General Public
13  * License as published by the Free Software Foundation; either
14  * version 2.1 of the License, or (at your option) any later version.
15  *
16  * This library is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19  * Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public
22  * License along with this library; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
24  */
25 
26 #include "../Foundation/Foundation.h"
27 
28 
29 @global CPApp
30 
31 // CPScroller Constants
39 
42 
46 
55 
56 var _CACHED_THEME_SCROLLER = nil; // This is used by the class methods to pull the theme attributes.
57 
58 NAMES_FOR_PARTS[CPScrollerDecrementLine] = @"decrement-line";
59 NAMES_FOR_PARTS[CPScrollerIncrementLine] = @"increment-line";
60 NAMES_FOR_PARTS[CPScrollerKnobSlot] = @"knob-slot";
62 
63 
66 
70 
71 CPThemeStateScrollViewLegacy = CPThemeState("scroller-style-legacy");
72 CPThemeStateScrollerKnobLight = CPThemeState("scroller-knob-light");
73 CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
74 
75 @implementation CPScroller : CPControl
76 {
77  CPControlSize _controlSize;
78  CPUsableScrollerParts _usableParts;
79  CPArray _partRects;
80 
81  BOOL _isVertical;
82  float _knobProportion;
83 
84  CPScrollerPart _hitPart;
85 
86  CPScrollerPart _trackingPart;
87  float _trackingFloatValue;
88  CGPoint _trackingStartPoint;
89 
90  CPViewAnimation _animationScroller;
91 
92  BOOL _allowFadingOut;
93  int _style;
94  CPTimer _timerFadeOut;
95  BOOL _isMouseOver;
96 }
97 
98 
99 #pragma mark -
100 #pragma mark Class methods
101 
102 + (CPString)defaultThemeClass
103 {
104  return "scroller";
105 }
106 
107 + (CPDictionary)themeAttributes
108 {
109  return @{
110  @"scroller-width": 7.0,
111  @"knob-slot-color": [CPNull null],
112  @"decrement-line-color": [CPNull null],
113  @"increment-line-color": [CPNull null],
114  @"knob-color": [CPNull null],
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
121  };
122 }
123 
124 + (float)scrollerWidth
125 {
126  return [self scrollerWidthInStyle:CPScrollerStyleLegacy];
127 }
128 
132 + (float)scrollerWidthInStyle:(int)aStyle
133 {
134  if (!_CACHED_THEME_SCROLLER)
135  _CACHED_THEME_SCROLLER = [[self alloc] init];
136 
137  if (aStyle == CPScrollerStyleLegacy)
138  return [_CACHED_THEME_SCROLLER valueForThemeAttribute:@"scroller-width" inState:CPThemeStateScrollViewLegacy];
139 
140  return [_CACHED_THEME_SCROLLER currentValueForThemeAttribute:@"scroller-width"];
141 }
142 
146 + (float)scrollerOverlay
147 {
148  if (!_CACHED_THEME_SCROLLER)
149  _CACHED_THEME_SCROLLER = [[self alloc] init];
150 
151  return [_CACHED_THEME_SCROLLER currentValueForThemeAttribute:@"track-border-overlay"];
152 }
153 
158 + (float)scrollerWidthForControlSize:(CPControlSize)aControlSize
159 {
160  return [self scrollerWidth];
161 }
162 
163 
164 #pragma mark -
165 #pragma mark Initialization
166 
167 - (id)initWithFrame:(CGRect)aFrame
168 {
169  if (self = [super initWithFrame:aFrame])
170  {
171  _controlSize = CPRegularControlSize;
172  _partRects = [];
173 
174  [self setFloatValue:0.0];
175  [self setKnobProportion:1.0];
176 
177  _hitPart = CPScrollerNoPart;
178  _allowFadingOut = YES;
179  _isMouseOver = NO;
180  _style = CPScrollerStyleOverlay;
181 
182  var paramAnimFadeOut = @{
183  CPViewAnimationTargetKey: self,
184  CPViewAnimationEffectKey: CPViewAnimationFadeOutEffect,
185  };
186 
187  _animationScroller = [[CPViewAnimation alloc] initWithDuration:0.2 animationCurve:CPAnimationEaseInOut];
188  [_animationScroller setViewAnimations:[paramAnimFadeOut]];
189  [_animationScroller setDelegate:self];
190  [self setAlphaValue:0.0];
191 
192  // We have to choose an orientation. If for some bizarre reason width === height,
193  // punt and choose vertical.
194  [self _setIsVertical:CGRectGetHeight(aFrame) >= CGRectGetWidth(aFrame)];
195  }
196 
197  return self;
198 }
199 
200 
201 #pragma mark -
202 #pragma mark Getters / Setters
203 
207 - (void)style
208 {
209  return _style;
210 }
211 
216 - (void)setStyle:(id)aStyle
217 {
218  if (_style != nil && _style === aStyle)
219  return;
220 
221  _style = aStyle;
222 
223  if (_style === CPScrollerStyleLegacy)
224  {
225  [self fadeIn];
226  [self setThemeState:CPThemeStateScrollViewLegacy];
227  }
228  else
229  {
230  _allowFadingOut = YES;
231  [self unsetThemeState:CPThemeStateScrollViewLegacy];
232  }
233 
234  //[self _adjustScrollerSize];
235 }
236 
237 - (void)setObjectValue:(id)aValue
238 {
239  [super setObjectValue:MIN(1.0, MAX(0.0, +aValue))];
240 }
241 
245 - (CPControlSize)controlSize
246 {
247  return _controlSize;
248 }
249 
254 - (void)setControlSize:(CPControlSize)aControlSize
255 {
256  if (_controlSize == aControlSize)
257  return;
258 
259  _controlSize = aControlSize;
260 
261  [self setNeedsLayout];
262  [self setNeedsDisplay:YES];
263 }
264 
268 - (float)knobProportion
269 {
270  return _knobProportion;
271 }
272 
277 - (void)setKnobProportion:(float)aProportion
278 {
279  if (!_IS_NUMERIC(aProportion))
280  [CPException raise:CPInvalidArgumentException reason:"aProportion must be numeric"];
281 
282  _knobProportion = MIN(1.0, MAX(0.0001, aProportion));
283 
284  [self setNeedsDisplay:YES];
285  [self setNeedsLayout];
286 }
287 
288 
289 #pragma mark -
290 #pragma mark Privates
291 
293 - (void)_adjustScrollerSize
294 {
295  var frame = [self frame],
296  scrollerWidth = [self currentValueForThemeAttribute:@"scroller-width"];
297 
298  if ([self isVertical] && CGRectGetWidth(frame) !== scrollerWidth)
299  frame.size.width = scrollerWidth;
300 
301  if (![self isVertical] && CGRectGetHeight(frame) !== scrollerWidth)
302  frame.size.height = scrollerWidth;
303 
304  [self setFrame:frame];
305 }
306 
308 - (void)_performFadeOut:(CPTimer)aTimer
309 {
310  [self fadeOut];
311  _timerFadeOut = nil;
312 }
313 
314 
315 #pragma mark -
316 #pragma mark Utilities
317 
318 - (CGRect)rectForPart:(CPScrollerPart)aPart
319 {
320  if (aPart == CPScrollerNoPart)
321  return CGRectMakeZero();
322 
323  return _partRects[aPart];
324 }
325 
331 - (CPScrollerPart)testPart:(CGPoint)aPoint
332 {
333  aPoint = [self convertPoint:aPoint fromView:nil];
334 
335  // The ordering of these tests is important. We check the knob and
336  // page rects first since they may overlap with the arrows.
337 
338  if (![self hasThemeState:CPThemeStateSelected])
339  return CPScrollerNoPart;
340 
341  if (CGRectContainsPoint([self rectForPart:CPScrollerKnob], aPoint))
342  return CPScrollerKnob;
343 
344  if (CGRectContainsPoint([self rectForPart:CPScrollerDecrementPage], aPoint))
346 
347  if (CGRectContainsPoint([self rectForPart:CPScrollerIncrementPage], aPoint))
349 
350  if (CGRectContainsPoint([self rectForPart:CPScrollerDecrementLine], aPoint))
352 
353  if (CGRectContainsPoint([self rectForPart:CPScrollerIncrementLine], aPoint))
355 
356  if (CGRectContainsPoint([self rectForPart:CPScrollerKnobSlot], aPoint))
357  return CPScrollerKnobSlot;
358 
359  return CPScrollerNoPart;
360 }
361 
365 - (void)checkSpaceForParts
366 {
367  var bounds = [self bounds];
368 
369  // Assume we won't be needing the arrows.
370  if (_knobProportion === 1.0)
371  {
372  _usableParts = CPNoScrollerParts;
373 
374  _partRects[CPScrollerDecrementPage] = CGRectMakeZero();
375  _partRects[CPScrollerKnob] = CGRectMakeZero();
376  _partRects[CPScrollerIncrementPage] = CGRectMakeZero();
377  _partRects[CPScrollerDecrementLine] = CGRectMakeZero();
378  _partRects[CPScrollerIncrementLine] = CGRectMakeZero();
379 
380  // In this case, the slot is the entirety of the scroller.
381  _partRects[CPScrollerKnobSlot] = CGRectMakeCopy(bounds);
382 
383  return;
384  }
385 
386  // At this point we know we're going to need arrows.
387  _usableParts = CPAllScrollerParts;
388 
389  var knobInset = [self currentValueForThemeAttribute:@"knob-inset"],
390  trackInset = [self currentValueForThemeAttribute:@"track-inset"],
391  width = CGRectGetWidth(bounds),
392  height = CGRectGetHeight(bounds);
393 
394  if ([self isVertical])
395  {
396  var decrementLineSize = [self currentValueForThemeAttribute:"decrement-line-size"],
397  incrementLineSize = [self currentValueForThemeAttribute:"increment-line-size"],
398  effectiveDecrementLineHeight = decrementLineSize.height + trackInset.top,
399  effectiveIncrementLineHeight = incrementLineSize.height + trackInset.bottom,
400  slotSize = height - effectiveDecrementLineHeight - effectiveIncrementLineHeight,
401  minimumKnobLength = [self currentValueForThemeAttribute:"minimum-knob-length"],
402  knobWidth = width - knobInset.left - knobInset.right,
403  knobHeight = MAX(minimumKnobLength, (slotSize * _knobProportion)),
404  knobLocation = effectiveDecrementLineHeight + (slotSize - knobHeight) * [self floatValue];
405 
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);
412 
413  if (height < knobHeight + decrementLineSize.height + incrementLineSize.height + trackInset.top + trackInset.bottom)
414  _partRects[CPScrollerKnob] = CGRectMakeZero();
415 
416  if (height < decrementLineSize.height + incrementLineSize.height - 2)
417  {
418  _partRects[CPScrollerIncrementLine] = CGRectMakeZero();
419  _partRects[CPScrollerDecrementLine] = CGRectMakeZero();
420  _partRects[CPScrollerKnobSlot] = CGRectMake(trackInset.left, 0, width - trackInset.left - trackInset.right, height);
421  }
422  }
423  else
424  {
425  var decrementLineSize = [self currentValueForThemeAttribute:"decrement-line-size"],
426  incrementLineSize = [self currentValueForThemeAttribute:"increment-line-size"],
427  effectiveDecrementLineWidth = decrementLineSize.width + trackInset.left,
428  effectiveIncrementLineWidth = incrementLineSize.width + trackInset.right,
429  slotSize = width - effectiveDecrementLineWidth - effectiveIncrementLineWidth,
430  minimumKnobLength = [self currentValueForThemeAttribute:"minimum-knob-length"],
431  knobWidth = MAX(minimumKnobLength, (slotSize * _knobProportion)),
432  knobHeight = height - knobInset.top - knobInset.bottom,
433  knobLocation = effectiveDecrementLineWidth + (slotSize - knobWidth) * [self floatValue];
434 
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);
441 
442  if (width < knobWidth + decrementLineSize.width + incrementLineSize.width + trackInset.left + trackInset.right)
443  _partRects[CPScrollerKnob] = CGRectMakeZero();
444 
445  if (width < decrementLineSize.width + incrementLineSize.width - 2)
446  {
447  _partRects[CPScrollerIncrementLine] = CGRectMakeZero();
448  _partRects[CPScrollerDecrementLine] = CGRectMakeZero();
449  _partRects[CPScrollerKnobSlot] = CGRectMake(0.0, 0.0, width, slotSize);
450  }
451  }
452 }
453 
458 - (CPUsableScrollerParts)usableParts
459 {
460  return _usableParts;
461 }
462 
466 - (void)fadeIn
467 {
468  if (_isMouseOver && _knobProportion != 1.0)
469  [self setThemeState:CPThemeStateSelected];
470 
471  if (_timerFadeOut)
472  [_timerFadeOut invalidate];
473 
474  [self setAlphaValue:1.0];
475 }
476 
480 - (void)fadeOut
481 {
482  if ([self hasThemeState:CPThemeStateScrollViewLegacy])
483  return;
484 
485  [_animationScroller startAnimation];
486 }
487 
488 
489 #pragma mark -
490 #pragma mark Drawing
491 
497 - (void)drawArrow:(CPScrollerArrow)anArrow highlight:(BOOL)shouldHighlight
498 {
499 }
500 
504 - (void)drawKnob
505 {
506 }
507 
511 - (void)drawKnobSlot
512 {
513 }
514 
515 - (CPView)createViewForPart:(CPScrollerPart)aPart
516 {
517  var view = [[CPView alloc] initWithFrame:CGRectMakeZero()];
518 
519  [view setHitTests:NO];
520 
521  return view;
522 }
523 
524 - (CGRect)rectForEphemeralSubviewNamed:(CPString)aName
525 {
526  return _partRects[aName];
527 }
528 
529 - (CPView)createEphemeralSubviewNamed:(CPString)aName
530 {
531  var view = [[CPView alloc] initWithFrame:CGRectMakeZero()];
532 
533  [view setHitTests:NO];
534 
535  return view;
536 }
537 
538 - (void)layoutSubviews
539 {
540  [self _adjustScrollerSize];
541  [self checkSpaceForParts];
542 
543  var index = 0,
544  count = PARTS_ARRANGEMENT.length,
545  view;
546 
547  for (; index < count; ++index)
548  {
549  var part = PARTS_ARRANGEMENT[index];
550 
551  if (index === 0)
552  view = [self layoutEphemeralSubviewNamed:part positioned:CPWindowBelow relativeToEphemeralSubviewNamed:PARTS_ARRANGEMENT[index + 1]];
553  else
554  view = [self layoutEphemeralSubviewNamed:part positioned:CPWindowAbove relativeToEphemeralSubviewNamed:PARTS_ARRANGEMENT[index - 1]];
555 
556  if (view)
557  [view setBackgroundColor:[self currentValueForThemeAttribute:NAMES_FOR_PARTS[part] + "-color"]];
558  }
559 }
560 
564 - (void)drawParts
565 {
566  [self drawKnobSlot];
567  [self drawKnob];
568  [self drawArrow:CPScrollerDecrementArrow highlight:NO];
569  [self drawArrow:CPScrollerIncrementArrow highlight:NO];
570 }
571 
572 // Event Handling
576 - (CPScrollerPart)hitPart
577 {
578  return _hitPart;
579 }
580 
585 - (void)trackKnob:(CPEvent)anEvent
586 {
587  var type = [anEvent type];
588 
589  if (type === CPLeftMouseUp)
590  {
591  _hitPart = CPScrollerNoPart;
592 
593  return;
594  }
595 
596  if (type === CPLeftMouseDown)
597  {
598  _trackingFloatValue = [self floatValue];
599  _trackingStartPoint = [self convertPoint:[anEvent locationInWindow] fromView:nil];
600  }
601 
602  else if (type === CPLeftMouseDragged)
603  {
604  var knobRect = [self rectForPart:CPScrollerKnob],
605  knobSlotRect = [self rectForPart:CPScrollerKnobSlot],
606  remainder = ![self isVertical] ? (CGRectGetWidth(knobSlotRect) - CGRectGetWidth(knobRect)) : (CGRectGetHeight(knobSlotRect) - CGRectGetHeight(knobRect));
607 
608  if (remainder <= 0)
609  [self setFloatValue:0.0];
610  else
611  {
612  var location = [self convertPoint:[anEvent locationInWindow] fromView:nil],
613  delta = ![self isVertical] ? location.x - _trackingStartPoint.x : location.y - _trackingStartPoint.y;
614 
615  [self setFloatValue:_trackingFloatValue + delta / remainder];
616  }
617  }
618 
619  [CPApp setTarget:self selector:@selector(trackKnob:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
620 
621  if (type === CPLeftMouseDragged)
622  [self sendAction:[self action] to:[self target]];
623 }
624 
629 - (void)trackScrollButtons:(CPEvent)anEvent
630 {
631  var type = [anEvent type];
632 
633  if (type === CPLeftMouseUp)
634  {
635  [self highlight:NO];
636  [CPEvent stopPeriodicEvents];
637 
638  _hitPart = CPScrollerNoPart;
639 
640  return;
641  }
642 
643  if (type === CPLeftMouseDown)
644  {
645  _trackingPart = [self hitPart];
646 
647  _trackingStartPoint = [self convertPoint:[anEvent locationInWindow] fromView:nil];
648 
649  if ([anEvent modifierFlags] & CPAlternateKeyMask)
650  {
651  if (_trackingPart === CPScrollerDecrementLine)
652  _hitPart = CPScrollerDecrementPage;
653 
654  else if (_trackingPart === CPScrollerIncrementLine)
655  _hitPart = CPScrollerIncrementPage;
656 
657  else if (_trackingPart === CPScrollerDecrementPage || _trackingPart === CPScrollerIncrementPage)
658  {
659  var knobRect = [self rectForPart:CPScrollerKnob],
660  knobWidth = ![self isVertical] ? CGRectGetWidth(knobRect) : CGRectGetHeight(knobRect),
661  knobSlotRect = [self rectForPart:CPScrollerKnobSlot],
662  remainder = (![self isVertical] ? CGRectGetWidth(knobSlotRect) : CGRectGetHeight(knobSlotRect)) - knobWidth;
663 
664  [self setFloatValue:((![self isVertical] ? _trackingStartPoint.x - CGRectGetMinX(knobSlotRect) : _trackingStartPoint.y - CGRectGetMinY(knobSlotRect)) - knobWidth / 2.0) / remainder];
665 
666  _hitPart = CPScrollerKnob;
667 
668  [self sendAction:[self action] to:[self target]];
669 
670  // Now just track the knob.
671  return [self trackKnob:anEvent];
672  }
673  }
674 
675  [self highlight:YES];
676  [self sendAction:[self action] to:[self target]];
677 
678  [CPEvent startPeriodicEventsAfterDelay:0.5 withPeriod:0.04];
679  }
680 
681  else if (type === CPLeftMouseDragged)
682  {
683  _trackingStartPoint = [self convertPoint:[anEvent locationInWindow] fromView:nil];
684 
685  if (_trackingPart === CPScrollerDecrementPage || _trackingPart === CPScrollerIncrementPage)
686  {
687  var hitPart = [self testPart:[anEvent locationInWindow]];
688 
689  if (hitPart === CPScrollerDecrementPage || hitPart === CPScrollerIncrementPage)
690  {
691  _trackingPart = hitPart;
692  _hitPart = hitPart;
693  }
694  }
695 
696  [self highlight:CGRectContainsPoint([self rectForPart:_trackingPart], _trackingStartPoint)];
697  }
698  else if (type == CPPeriodic && CGRectContainsPoint([self rectForPart:_trackingPart], _trackingStartPoint))
699  [self sendAction:[self action] to:[self target]];
700 
701  [CPApp setTarget:self selector:@selector(trackScrollButtons:) forNextEventMatchingMask:CPPeriodicMask | CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
702 
703 }
704 
705 - (void)_setIsVertical:(BOOL)isVertical
706 {
707  _isVertical = isVertical;
708 
709  if (_isVertical)
710  [self setThemeState:CPThemeStateVertical];
711  else
712  [self unsetThemeState:CPThemeStateVertical];
713 }
714 
715 - (void)setFrameSize:(CGSize)aSize
716 {
717  [super setFrameSize:aSize];
718 
719  [self checkSpaceForParts];
720  [self setNeedsLayout];
721 }
722 
723 
724 #pragma mark -
725 #pragma mark Overrides
726 
727 - (id)currentValueForThemeAttribute:(CPString)anAttributeName
728 {
729  var themeState = _themeState;
730 
731  if (NAMES_FOR_PARTS[_hitPart] + "-color" !== anAttributeName)
732  themeState &= ~CPThemeStateHighlighted;
733 
734  return [self valueForThemeAttribute:anAttributeName inState:themeState];
735 }
736 
737 - (void)mouseDown:(CPEvent)anEvent
738 {
739  if (![self isEnabled])
740  return;
741 
742  _hitPart = [self testPart:[anEvent locationInWindow]];
743 
744  switch (_hitPart)
745  {
746  case CPScrollerKnob:
747  return [self trackKnob:anEvent];
748 
753  return [self trackScrollButtons:anEvent];
754  }
755 }
756 
757 - (void)mouseEntered:(CPEvent)anEvent
758 {
759  [super mouseEntered:anEvent];
760 
761  if (_timerFadeOut)
762  [_timerFadeOut invalidate];
763 
764  if (![self isEnabled])
765  return;
766 
767  _allowFadingOut = NO;
768  _isMouseOver = YES;
769 
770  if ([self alphaValue] > 0 && _knobProportion != 1.0)
771  [self setThemeState:CPThemeStateSelected];
772 }
773 
774 - (void)mouseExited:(CPEvent)anEvent
775 {
776  [super mouseExited:anEvent];
777 
778  if ([self isHidden] || ![self isEnabled] || !_isMouseOver)
779  return;
780 
781  _allowFadingOut = YES;
782  _isMouseOver = NO;
783 
784  if (_timerFadeOut)
785  [_timerFadeOut invalidate];
786 
787  _timerFadeOut = [CPTimer scheduledTimerWithTimeInterval:1.2 target:self selector:@selector(_performFadeOut:) userInfo:nil repeats:NO];
788 }
789 
790 
791 #pragma mark -
792 #pragma mark Delegates
793 
794 - (void)animationDidEnd:(CPAnimation)animation
795 {
796  [self unsetThemeState:CPThemeStateSelected];
797 }
798 
799 @end
800 
801 var CPScrollerControlSizeKey = @"CPScrollerControlSize",
802  CPScrollerIsVerticalKey = @"CPScrollerIsVerticalKey",
803  CPScrollerKnobProportionKey = @"CPScrollerKnobProportion",
804  CPScrollerStyleKey = @"CPScrollerStyleKey";
805 
806 @implementation CPScroller (CPCoding)
807 
808 - (id)initWithCoder:(CPCoder)aCoder
809 {
810  if (self = [super initWithCoder:aCoder])
811  {
812  _controlSize = CPRegularControlSize;
813  if ([aCoder containsValueForKey:CPScrollerControlSizeKey])
814  _controlSize = [aCoder decodeIntForKey:CPScrollerControlSizeKey];
815 
816  _knobProportion = 1.0;
817 
818  if ([aCoder containsValueForKey:CPScrollerKnobProportionKey])
819  _knobProportion = [aCoder decodeFloatForKey:CPScrollerKnobProportionKey];
820 
821  _partRects = [];
822 
823  _hitPart = CPScrollerNoPart;
824 
825  _allowFadingOut = YES;
826  _isMouseOver = NO;
827 
828  var paramAnimFadeOut = @{
829  CPViewAnimationTargetKey: self,
830  CPViewAnimationEffectKey: CPViewAnimationFadeOutEffect,
831  };
832 
833  _animationScroller = [[CPViewAnimation alloc] initWithDuration:0.2 animationCurve:CPAnimationEaseInOut];
834  [_animationScroller setViewAnimations:[paramAnimFadeOut]];
835  [_animationScroller setDelegate:self];
836  [self setAlphaValue:0.0];
837 
838  [self setStyle:[aCoder decodeIntForKey:CPScrollerStyleKey]];
839 
840  [self _setIsVertical:[aCoder decodeBoolForKey:CPScrollerIsVerticalKey]];
841  }
842 
843  return self;
844 }
845 
846 - (void)encodeWithCoder:(CPCoder)aCoder
847 {
848  [super encodeWithCoder:aCoder];
849 
850  [aCoder encodeInt:_controlSize forKey:CPScrollerControlSizeKey];
851  [aCoder encodeInt:_isVertical forKey:CPScrollerIsVerticalKey];
852  [aCoder encodeFloat:_knobProportion forKey:CPScrollerKnobProportionKey];
853  [aCoder encodeInt:_style forKey:CPScrollerStyleKey];
854 }
855 
856 @end
857 
858 @implementation CPScroller (Deprecated)
859 
865 - (void)setFloatValue:(float)aValue knobProportion:(float)aProportion
866 {
867  [self setFloatValue:aValue];
868  [self setKnobProportion:aProportion];
869 }
870 
871 @end
872 
874 
878 - (BOOL)isVertical
879 {
880  return _isVertical;
881 }
882 
886 - (BOOL)allowFadingOut
887 {
888  return _allowFadingOut;
889 }
890 
891 @end