1// Copyright (c) 2011 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 "chrome/browser/ui/views/infobars/infobar.h"
6
7#include <cmath>
8
9#include "ui/base/animation/slide_animation.h"
10#include "chrome/browser/tab_contents/infobar_delegate.h"
11#include "chrome/browser/ui/views/infobars/infobar_container.h"
12
13InfoBar::InfoBar(InfoBarDelegate* delegate)
14    : delegate_(delegate),
15      container_(NULL),
16      ALLOW_THIS_IN_INITIALIZER_LIST(animation_(new ui::SlideAnimation(this))),
17      arrow_height_(0),
18      arrow_target_height_(kDefaultArrowTargetHeight),
19      arrow_half_width_(0),
20      bar_height_(0),
21      bar_target_height_(kDefaultBarTargetHeight) {
22  DCHECK(delegate != NULL);
23  animation_->SetTweenType(ui::Tween::LINEAR);
24}
25
26InfoBar::~InfoBar() {
27}
28
29void InfoBar::Show(bool animate) {
30  if (animate) {
31    animation_->Show();
32  } else {
33    animation_->Reset(1.0);
34    AnimationEnded(NULL);
35  }
36}
37
38void InfoBar::Hide(bool animate) {
39  PlatformSpecificHide(animate);
40  if (animate) {
41    animation_->Hide();
42  } else {
43    animation_->Reset(0.0);
44    AnimationEnded(NULL);
45  }
46}
47
48void InfoBar::SetArrowTargetHeight(int height) {
49  DCHECK_LE(height, kMaximumArrowTargetHeight);
50  // Once the closing animation starts, we ignore further requests to change the
51  // target height.
52  if ((arrow_target_height_ != height) && !animation()->IsClosing()) {
53    arrow_target_height_ = height;
54    RecalculateHeights(false);
55  }
56}
57
58void InfoBar::AnimationProgressed(const ui::Animation* animation) {
59  RecalculateHeights(false);
60}
61
62void InfoBar::RemoveInfoBar() {
63  if (container_)
64    container_->RemoveDelegate(delegate_);
65}
66
67void InfoBar::SetBarTargetHeight(int height) {
68  if (bar_target_height_ != height) {
69    bar_target_height_ = height;
70    RecalculateHeights(false);
71  }
72}
73
74int InfoBar::OffsetY(const gfx::Size& prefsize) const {
75  return arrow_height_ +
76      std::max((bar_target_height_ - prefsize.height()) / 2, 0) -
77      (bar_target_height_ - bar_height_);
78}
79
80void InfoBar::AnimationEnded(const ui::Animation* animation) {
81  // When the animation ends, we must ensure the container is notified even if
82  // the heights haven't changed, lest it never get an "animation finished"
83  // notification.  (If the browser doesn't get this notification, it will not
84  // bother to re-layout the content area for the new infobar size.)
85  RecalculateHeights(true);
86  MaybeDelete();
87}
88
89void InfoBar::RecalculateHeights(bool force_notify) {
90  int old_arrow_height = arrow_height_;
91  int old_bar_height = bar_height_;
92
93  // Find the desired arrow height/half-width.  The arrow area is
94  // |arrow_height_| * |arrow_half_width_|.  When the bar is opening or closing,
95  // scaling each of these with the square root of the animation value causes a
96  // linear animation of the area, which matches the perception of the animation
97  // of the bar portion.
98  double scale_factor = sqrt(animation()->GetCurrentValue());
99  arrow_height_ = static_cast<int>(arrow_target_height_ * scale_factor);
100  if (animation_->is_animating()) {
101    arrow_half_width_ = static_cast<int>(std::min(arrow_target_height_,
102        kMaximumArrowTargetHalfWidth) * scale_factor);
103  } else {
104    // When the infobar is not animating (i.e. fully open), we set the
105    // half-width to be proportionally the same distance between its default and
106    // maximum values as the height is between its.
107    arrow_half_width_ = kDefaultArrowTargetHalfWidth +
108        ((kMaximumArrowTargetHalfWidth - kDefaultArrowTargetHalfWidth) *
109         ((arrow_height_ - kDefaultArrowTargetHeight) /
110          (kMaximumArrowTargetHeight - kDefaultArrowTargetHeight)));
111  }
112  // Add pixels for the stroke, if the arrow is to be visible at all.  Without
113  // this, changing the arrow height from 0 to kSeparatorLineHeight would
114  // produce no visible effect, because the stroke would paint atop the divider
115  // line above the infobar.
116  if (arrow_height_)
117    arrow_height_ += kSeparatorLineHeight;
118
119  bar_height_ =
120      static_cast<int>(bar_target_height_ * animation()->GetCurrentValue());
121
122  // Don't re-layout if nothing has changed, e.g. because the animation step was
123  // not large enough to actually change the heights by at least a pixel.
124  bool heights_differ =
125      (old_arrow_height != arrow_height_) || (old_bar_height != bar_height_);
126  if (heights_differ)
127    PlatformSpecificOnHeightsRecalculated();
128
129  if (container_ && (heights_differ || force_notify))
130    container_->OnInfoBarStateChanged(animation_->is_animating());
131}
132
133void InfoBar::MaybeDelete() {
134  if (delegate_ && (animation_->GetCurrentValue() == 0.0)) {
135    if (container_)
136      container_->RemoveInfoBar(this);
137    // Note that we only tell the delegate we're closed here, and not when we're
138    // simply destroyed (by virtue of a tab switch or being moved from window to
139    // window), since this action can cause the delegate to destroy itself.
140    delegate_->InfoBarClosed();
141    delegate_ = NULL;
142  }
143}
144