1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #import <Cocoa/Cocoa.h> 6 #import <QuartzCore/QuartzCore.h> 7 8 #import "chrome/browser/ui/cocoa/animatable_view.h" 9 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSAnimation+Duration.h" 10 11 // NSAnimation subclass that animates the height of an AnimatableView. Allows 12 // the caller to start and cancel the animation as desired. 13 @interface HeightAnimation : NSAnimation { 14 @private 15 AnimatableView* view_; // weak, owns us. 16 CGFloat startHeight_; 17 CGFloat endHeight_; 18 } 19 20 // Initialize a new height animation for the given view. The animation will not 21 // start until startAnimation: is called. 22 - (id)initWithView:(AnimatableView*)view 23 finalHeight:(CGFloat)height 24 duration:(NSTimeInterval)duration; 25 @end 26 27 @implementation HeightAnimation 28 - (id)initWithView:(AnimatableView*)view 29 finalHeight:(CGFloat)height 30 duration:(NSTimeInterval)duration { 31 if ((self = [super gtm_initWithDuration:duration 32 eventMask:NSLeftMouseUpMask 33 animationCurve:NSAnimationEaseIn])) { 34 view_ = view; 35 startHeight_ = [view_ height]; 36 endHeight_ = height; 37 [self setAnimationBlockingMode:NSAnimationNonblocking]; 38 [self setDelegate:view_]; 39 } 40 return self; 41 } 42 43 // Overridden to call setHeight for each progress tick. 44 - (void)setCurrentProgress:(NSAnimationProgress)progress { 45 [super setCurrentProgress:progress]; 46 [view_ setHeight:((progress * (endHeight_ - startHeight_)) + startHeight_)]; 47 } 48 @end 49 50 51 @implementation AnimatableView 52 @synthesize delegate = delegate_; 53 @synthesize resizeDelegate = resizeDelegate_; 54 55 - (void)dealloc { 56 // Stop the animation if it is running, since it holds a pointer to this view. 57 [self stopAnimation]; 58 [super dealloc]; 59 } 60 61 - (CGFloat)height { 62 return [self frame].size.height; 63 } 64 65 - (void)setHeight:(CGFloat)newHeight { 66 // Force the height to be an integer because some animations look terrible 67 // with non-integer intermediate heights. We only ever set integer heights 68 // for our views, so this shouldn't be a limitation in practice. 69 int height = floor(newHeight); 70 [resizeDelegate_ resizeView:self newHeight:height]; 71 } 72 73 - (void)animateToNewHeight:(CGFloat)newHeight 74 duration:(NSTimeInterval)duration { 75 [currentAnimation_ stopAnimation]; 76 77 currentAnimation_.reset([[HeightAnimation alloc] initWithView:self 78 finalHeight:newHeight 79 duration:duration]); 80 if ([resizeDelegate_ respondsToSelector:@selector(setAnimationInProgress:)]) 81 [resizeDelegate_ setAnimationInProgress:YES]; 82 [currentAnimation_ startAnimation]; 83 } 84 85 - (void)stopAnimation { 86 [currentAnimation_ stopAnimation]; 87 } 88 89 - (NSAnimationProgress)currentAnimationProgress { 90 return [currentAnimation_ currentProgress]; 91 } 92 93 - (void)animationDidStop:(NSAnimation*)animation { 94 if ([resizeDelegate_ respondsToSelector:@selector(setAnimationInProgress:)]) 95 [resizeDelegate_ setAnimationInProgress:NO]; 96 if ([delegate_ respondsToSelector:@selector(animationDidStop:)]) 97 [delegate_ animationDidStop:animation]; 98 currentAnimation_.reset(nil); 99 } 100 101 - (void)animationDidEnd:(NSAnimation*)animation { 102 if ([resizeDelegate_ respondsToSelector:@selector(setAnimationInProgress:)]) 103 [resizeDelegate_ setAnimationInProgress:NO]; 104 if ([delegate_ respondsToSelector:@selector(animationDidEnd:)]) 105 [delegate_ animationDidEnd:animation]; 106 currentAnimation_.reset(nil); 107 } 108 109 @end 110