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