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(View* view,
114                                          AnimationDelegate* delegate,
115                                          bool delete_when_done) {
116  DCHECK(IsAnimating(view));
117
118  data_[view].delegate = delegate;
119  data_[view].delete_delegate_when_done = delete_when_done;
120}
121
122void BoundsAnimator::StopAnimatingView(View* view) {
123  if (!IsAnimating(view))
124    return;
125
126  data_[view].animation->Stop();
127}
128
129bool BoundsAnimator::IsAnimating(View* view) const {
130  return data_.find(view) != data_.end();
131}
132
133bool BoundsAnimator::IsAnimating() const {
134  return !data_.empty();
135}
136
137void BoundsAnimator::Cancel() {
138  if (data_.empty())
139    return;
140
141  while (!data_.empty())
142    data_.begin()->second.animation->Stop();
143
144  // Invoke AnimationContainerProgressed to force a repaint and notify delegate.
145  AnimationContainerProgressed(container_.get());
146}
147
148void BoundsAnimator::SetAnimationDuration(int duration_ms) {
149  animation_duration_ms_ = duration_ms;
150}
151
152void BoundsAnimator::AddObserver(BoundsAnimatorObserver* observer) {
153  observers_.AddObserver(observer);
154}
155
156void BoundsAnimator::RemoveObserver(BoundsAnimatorObserver* observer) {
157  observers_.RemoveObserver(observer);
158}
159
160SlideAnimation* BoundsAnimator::CreateAnimation() {
161  SlideAnimation* animation = new SlideAnimation(this);
162  animation->SetContainer(container_.get());
163  animation->SetSlideDuration(animation_duration_ms_);
164  animation->SetTweenType(tween_type_);
165  return animation;
166}
167
168void BoundsAnimator::RemoveFromMaps(View* view) {
169  DCHECK(data_.count(view) > 0);
170  DCHECK(animation_to_view_.count(data_[view].animation) > 0);
171
172  animation_to_view_.erase(data_[view].animation);
173  data_.erase(view);
174}
175
176void BoundsAnimator::CleanupData(bool send_cancel, Data* data, View* view) {
177  if (send_cancel && data->delegate)
178    data->delegate->AnimationCanceled(data->animation);
179
180  if (data->delete_delegate_when_done) {
181    delete static_cast<OwnedAnimationDelegate*>(data->delegate);
182    data->delegate = NULL;
183  }
184
185  if (data->animation) {
186    data->animation->set_delegate(NULL);
187    delete data->animation;
188    data->animation = NULL;
189  }
190}
191
192Animation* BoundsAnimator::ResetAnimationForView(View* view) {
193  if (!IsAnimating(view))
194    return NULL;
195
196  Animation* old_animation = data_[view].animation;
197  animation_to_view_.erase(old_animation);
198  data_[view].animation = NULL;
199  // Reset the delegate so that we don't attempt any processing when the
200  // animation calls us back.
201  old_animation->set_delegate(NULL);
202  return old_animation;
203}
204
205void BoundsAnimator::AnimationEndedOrCanceled(const Animation* animation,
206                                              AnimationEndType type) {
207  DCHECK(animation_to_view_.find(animation) != animation_to_view_.end());
208
209  View* view = animation_to_view_[animation];
210  DCHECK(view);
211
212  // Make a copy of the data as Remove empties out the maps.
213  Data data = data_[view];
214
215  RemoveFromMaps(view);
216
217  if (data.delegate) {
218    if (type == ANIMATION_ENDED) {
219      data.delegate->AnimationEnded(animation);
220    } else {
221      DCHECK_EQ(ANIMATION_CANCELED, type);
222      data.delegate->AnimationCanceled(animation);
223    }
224  }
225
226  CleanupData(false, &data, view);
227}
228
229void BoundsAnimator::AnimationProgressed(const Animation* animation) {
230  DCHECK(animation_to_view_.find(animation) != animation_to_view_.end());
231
232  View* view = animation_to_view_[animation];
233  DCHECK(view);
234  const Data& data = data_[view];
235  gfx::Rect new_bounds =
236      animation->CurrentValueBetween(data.start_bounds, data.target_bounds);
237  if (new_bounds != view->bounds()) {
238    gfx::Rect total_bounds = gfx::UnionRects(new_bounds, view->bounds());
239
240    // Build up the region to repaint in repaint_bounds_. We'll do the repaint
241    // when all animations complete (in AnimationContainerProgressed).
242    repaint_bounds_.Union(total_bounds);
243
244    view->SetBoundsRect(new_bounds);
245  }
246
247  if (data.delegate)
248    data.delegate->AnimationProgressed(animation);
249}
250
251void BoundsAnimator::AnimationEnded(const Animation* animation) {
252  AnimationEndedOrCanceled(animation, ANIMATION_ENDED);
253}
254
255void BoundsAnimator::AnimationCanceled(const Animation* animation) {
256  AnimationEndedOrCanceled(animation, ANIMATION_CANCELED);
257}
258
259void BoundsAnimator::AnimationContainerProgressed(
260    AnimationContainer* container) {
261  if (!repaint_bounds_.IsEmpty()) {
262    // Adjust for rtl.
263    repaint_bounds_.set_x(parent_->GetMirroredXWithWidthInView(
264        repaint_bounds_.x(), repaint_bounds_.width()));
265    parent_->SchedulePaintInRect(repaint_bounds_);
266    repaint_bounds_.SetRect(0, 0, 0, 0);
267  }
268
269  FOR_EACH_OBSERVER(BoundsAnimatorObserver,
270                    observers_,
271                    OnBoundsAnimatorProgressed(this));
272
273  if (!IsAnimating()) {
274    // Notify here rather than from AnimationXXX to avoid deleting the animation
275    // while the animation is calling us.
276    FOR_EACH_OBSERVER(BoundsAnimatorObserver,
277                      observers_,
278                      OnBoundsAnimatorDone(this));
279  }
280}
281
282void BoundsAnimator::AnimationContainerEmpty(AnimationContainer* container) {
283}
284
285}  // namespace views
286