1// Copyright (c) 2012 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#include "ui/views/animation/bounds_animator.h"
6
7#include "base/memory/scoped_ptr.h"
8#include "ui/gfx/animation/animation_container.h"
9#include "ui/gfx/animation/slide_animation.h"
10#include "ui/views/animation/bounds_animator_observer.h"
11#include "ui/views/view.h"
12
13// Duration in milliseconds for animations.
14static const int kDefaultAnimationDuration = 200;
15
16using gfx::Animation;
17using gfx::AnimationContainer;
18using gfx::SlideAnimation;
19using gfx::Tween;
20
21namespace views {
22
23BoundsAnimator::BoundsAnimator(View* parent)
24    : parent_(parent),
25      container_(new AnimationContainer()),
26      animation_duration_ms_(kDefaultAnimationDuration),
27      tween_type_(Tween::EASE_OUT) {
28  container_->set_observer(this);
29}
30
31BoundsAnimator::~BoundsAnimator() {
32  // Reset the delegate so that we don't attempt to notify our observer from
33  // the destructor.
34  container_->set_observer(NULL);
35
36  // Delete all the animations, but don't remove any child views. We assume the
37  // view owns us and is going to be deleted anyway.
38  for (ViewToDataMap::iterator i = data_.begin(); i != data_.end(); ++i)
39    CleanupData(false, &(i->second), i->first);
40}
41
42void BoundsAnimator::AnimateViewTo(View* view, const gfx::Rect& target) {
43  DCHECK(view);
44  DCHECK_EQ(view->parent(), parent_);
45
46  Data existing_data;
47
48  if (IsAnimating(view)) {
49    // Don't immediatly delete the animation, that might trigger a callback from
50    // the animationcontainer.
51    existing_data = data_[view];
52
53    RemoveFromMaps(view);
54  }
55
56  // NOTE: we don't check if the view is already at the target location. Doing
57  // so leads to odd cases where no animations may be present after invoking
58  // AnimateViewTo. AnimationProgressed does nothing when the bounds of the
59  // view don't change.
60
61  Data& data = data_[view];
62  data.start_bounds = view->bounds();
63  data.target_bounds = target;
64  data.animation = CreateAnimation();
65
66  animation_to_view_[data.animation] = view;
67
68  data.animation->Show();
69
70  CleanupData(true, &existing_data, NULL);
71}
72
73void BoundsAnimator::SetTargetBounds(View* view, const gfx::Rect& target) {
74  if (!IsAnimating(view)) {
75    AnimateViewTo(view, target);
76    return;
77  }
78
79  data_[view].target_bounds = target;
80}
81
82gfx::Rect BoundsAnimator::GetTargetBounds(View* view) {
83  if (!IsAnimating(view))
84    return view->bounds();
85  return data_[view].target_bounds;
86}
87
88void BoundsAnimator::SetAnimationForView(View* view,
89                                         SlideAnimation* animation) {
90  DCHECK(animation);
91
92  scoped_ptr<SlideAnimation> animation_wrapper(animation);
93
94  if (!IsAnimating(view))
95    return;
96
97  // We delay deleting the animation until the end so that we don't prematurely
98  // send out notification that we're done.
99  scoped_ptr<Animation> old_animation(ResetAnimationForView(view));
100
101  data_[view].animation = animation_wrapper.release();
102  animation_to_view_[animation] = view;
103
104  animation->set_delegate(this);
105  animation->SetContainer(container_.get());
106  animation->Show();
107}
108
109const SlideAnimation* BoundsAnimator::GetAnimationForView(View* view) {
110  return !IsAnimating(view) ? NULL : data_[view].animation;
111}
112
113void BoundsAnimator::SetAnimationDelegate(
114    View* view,
115    scoped_ptr<AnimationDelegate> delegate) {
116  DCHECK(IsAnimating(view));
117
118  data_[view].delegate = delegate.release();
119}
120
121void BoundsAnimator::StopAnimatingView(View* view) {
122  if (!IsAnimating(view))
123    return;
124
125  data_[view].animation->Stop();
126}
127
128bool BoundsAnimator::IsAnimating(View* view) const {
129  return data_.find(view) != data_.end();
130}
131
132bool BoundsAnimator::IsAnimating() const {
133  return !data_.empty();
134}
135
136void BoundsAnimator::Cancel() {
137  if (data_.empty())
138    return;
139
140  while (!data_.empty())
141    data_.begin()->second.animation->Stop();
142
143  // Invoke AnimationContainerProgressed to force a repaint and notify delegate.
144  AnimationContainerProgressed(container_.get());
145}
146
147void BoundsAnimator::SetAnimationDuration(int duration_ms) {
148  animation_duration_ms_ = duration_ms;
149}
150
151void BoundsAnimator::AddObserver(BoundsAnimatorObserver* observer) {
152  observers_.AddObserver(observer);
153}
154
155void BoundsAnimator::RemoveObserver(BoundsAnimatorObserver* observer) {
156  observers_.RemoveObserver(observer);
157}
158
159SlideAnimation* BoundsAnimator::CreateAnimation() {
160  SlideAnimation* animation = new SlideAnimation(this);
161  animation->SetContainer(container_.get());
162  animation->SetSlideDuration(animation_duration_ms_);
163  animation->SetTweenType(tween_type_);
164  return animation;
165}
166
167void BoundsAnimator::RemoveFromMaps(View* view) {
168  DCHECK(data_.count(view) > 0);
169  DCHECK(animation_to_view_.count(data_[view].animation) > 0);
170
171  animation_to_view_.erase(data_[view].animation);
172  data_.erase(view);
173}
174
175void BoundsAnimator::CleanupData(bool send_cancel, Data* data, View* view) {
176  if (send_cancel && data->delegate)
177    data->delegate->AnimationCanceled(data->animation);
178
179  delete data->delegate;
180  data->delegate = NULL;
181
182  if (data->animation) {
183    data->animation->set_delegate(NULL);
184    delete data->animation;
185    data->animation = NULL;
186  }
187}
188
189Animation* BoundsAnimator::ResetAnimationForView(View* view) {
190  if (!IsAnimating(view))
191    return NULL;
192
193  Animation* old_animation = data_[view].animation;
194  animation_to_view_.erase(old_animation);
195  data_[view].animation = NULL;
196  // Reset the delegate so that we don't attempt any processing when the
197  // animation calls us back.
198  old_animation->set_delegate(NULL);
199  return old_animation;
200}
201
202void BoundsAnimator::AnimationEndedOrCanceled(const Animation* animation,
203                                              AnimationEndType type) {
204  DCHECK(animation_to_view_.find(animation) != animation_to_view_.end());
205
206  View* view = animation_to_view_[animation];
207  DCHECK(view);
208
209  // Make a copy of the data as Remove empties out the maps.
210  Data data = data_[view];
211
212  RemoveFromMaps(view);
213
214  if (data.delegate) {
215    if (type == ANIMATION_ENDED) {
216      data.delegate->AnimationEnded(animation);
217    } else {
218      DCHECK_EQ(ANIMATION_CANCELED, type);
219      data.delegate->AnimationCanceled(animation);
220    }
221  }
222
223  CleanupData(false, &data, view);
224}
225
226void BoundsAnimator::AnimationProgressed(const Animation* animation) {
227  DCHECK(animation_to_view_.find(animation) != animation_to_view_.end());
228
229  View* view = animation_to_view_[animation];
230  DCHECK(view);
231  const Data& data = data_[view];
232  gfx::Rect new_bounds =
233      animation->CurrentValueBetween(data.start_bounds, data.target_bounds);
234  if (new_bounds != view->bounds()) {
235    gfx::Rect total_bounds = gfx::UnionRects(new_bounds, view->bounds());
236
237    // Build up the region to repaint in repaint_bounds_. We'll do the repaint
238    // when all animations complete (in AnimationContainerProgressed).
239    repaint_bounds_.Union(total_bounds);
240
241    view->SetBoundsRect(new_bounds);
242  }
243
244  if (data.delegate)
245    data.delegate->AnimationProgressed(animation);
246}
247
248void BoundsAnimator::AnimationEnded(const Animation* animation) {
249  AnimationEndedOrCanceled(animation, ANIMATION_ENDED);
250}
251
252void BoundsAnimator::AnimationCanceled(const Animation* animation) {
253  AnimationEndedOrCanceled(animation, ANIMATION_CANCELED);
254}
255
256void BoundsAnimator::AnimationContainerProgressed(
257    AnimationContainer* container) {
258  if (!repaint_bounds_.IsEmpty()) {
259    // Adjust for rtl.
260    repaint_bounds_.set_x(parent_->GetMirroredXWithWidthInView(
261        repaint_bounds_.x(), repaint_bounds_.width()));
262    parent_->SchedulePaintInRect(repaint_bounds_);
263    repaint_bounds_.SetRect(0, 0, 0, 0);
264  }
265
266  FOR_EACH_OBSERVER(BoundsAnimatorObserver,
267                    observers_,
268                    OnBoundsAnimatorProgressed(this));
269
270  if (!IsAnimating()) {
271    // Notify here rather than from AnimationXXX to avoid deleting the animation
272    // while the animation is calling us.
273    FOR_EACH_OBSERVER(BoundsAnimatorObserver,
274                      observers_,
275                      OnBoundsAnimatorDone(this));
276  }
277}
278
279void BoundsAnimator::AnimationContainerEmpty(AnimationContainer* container) {
280}
281
282}  // namespace views
283