toast_contents_view.cc revision ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16
1// Copyright (c) 2013 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/message_center/views/toast_contents_view.h"
6
7#include "base/bind.h"
8#include "base/compiler_specific.h"
9#include "base/memory/scoped_ptr.h"
10#include "base/memory/weak_ptr.h"
11#include "base/time/time.h"
12#include "base/timer/timer.h"
13#include "ui/base/accessibility/accessible_view_state.h"
14#include "ui/base/animation/animation_delegate.h"
15#include "ui/base/animation/slide_animation.h"
16#include "ui/gfx/display.h"
17#include "ui/gfx/screen.h"
18#include "ui/message_center/message_center.h"
19#include "ui/message_center/message_center_style.h"
20#include "ui/message_center/notification.h"
21#include "ui/message_center/views/message_popup_collection.h"
22#include "ui/message_center/views/message_view.h"
23#include "ui/views/view.h"
24#include "ui/views/widget/widget.h"
25#include "ui/views/widget/widget_delegate.h"
26
27namespace message_center {
28namespace {
29
30// The width of a toast before animated reveal and after closing.
31const int kClosedToastWidth = 5;
32
33// FadeIn/Out look a bit better if they are slightly longer then default slide.
34const int kFadeInOutDuration = 200;
35
36}  // namespace.
37
38// static
39gfx::Size ToastContentsView::GetToastSizeForView(views::View* view) {
40  int width = kNotificationWidth + view->GetInsets().width();
41  return gfx::Size(width, view->GetHeightForWidth(width));
42}
43
44ToastContentsView::ToastContentsView(
45  const Notification* notification,
46  base::WeakPtr<MessagePopupCollection> collection,
47  MessageCenter* message_center)
48    : collection_(collection),
49      message_center_(message_center),
50      id_(notification->id()),
51      is_animating_bounds_(false),
52      is_closing_(false),
53      closing_animation_(NULL) {
54  DCHECK(collection_);
55
56  set_notify_enter_exit_on_child(true);
57  // Sets the transparent background. Then, when the message view is slid out,
58  // the whole toast seems to slide although the actual bound of the widget
59  // remains. This is hacky but easier to keep the consistency.
60  set_background(views::Background::CreateSolidBackground(0, 0, 0, 0));
61
62  fade_animation_.reset(new ui::SlideAnimation(this));
63  fade_animation_->SetSlideDuration(kFadeInOutDuration);
64}
65
66// This is destroyed when the toast window closes.
67ToastContentsView::~ToastContentsView() {
68}
69
70views::Widget* ToastContentsView::CreateWidget(gfx::NativeView parent) {
71  views::Widget::InitParams params(
72      views::Widget::InitParams::TYPE_POPUP);
73  params.keep_on_top = true;
74  if (parent)
75    params.parent = parent;
76  else
77    params.top_level = true;
78  params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
79  params.delegate = this;
80  views::Widget* widget = new views::Widget();
81  widget->set_focus_on_creation(false);
82  widget->Init(params);
83  return widget;
84}
85
86void ToastContentsView::SetContents(MessageView* view) {
87  bool already_has_contents = child_count() > 0;
88  RemoveAllChildViews(true);
89  AddChildView(view);
90  preferred_size_ = GetToastSizeForView(view);
91  Layout();
92  // If it has the contents already, this invocation means an update of the
93  // popup toast, and the new contents should be read through a11y feature.
94  if (already_has_contents)
95    NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_FOCUS, false);
96}
97
98void ToastContentsView::RevealWithAnimation(gfx::Point origin) {
99  // Place/move the toast widgets. Currently it stacks the widgets from the
100  // right-bottom of the work area.
101  // TODO(mukai): allow to specify the placement policy from outside of this
102  // class. The policy should be specified from preference on Windows, or
103  // the launcher alignment on ChromeOS.
104  origin_ = gfx::Point(origin.x() - preferred_size_.width(),
105                       origin.y() - preferred_size_.height());
106
107  gfx::Rect stable_bounds(origin_, preferred_size_);
108
109  SetBoundsInstantly(GetClosedToastBounds(stable_bounds));
110  StartFadeIn();
111  SetBoundsWithAnimation(stable_bounds);
112}
113
114void ToastContentsView::CloseWithAnimation(bool mark_as_shown) {
115  if (is_closing_)
116    return;
117  is_closing_ = true;
118  if (collection_)
119    collection_->RemoveToast(this);
120  if (mark_as_shown)
121    message_center_->MarkSinglePopupAsShown(id(), false);
122  StartFadeOut();
123}
124
125void ToastContentsView::SetBoundsInstantly(gfx::Rect new_bounds) {
126  if (new_bounds == bounds())
127    return;
128
129  origin_ = new_bounds.origin();
130  if (!GetWidget())
131    return;
132  GetWidget()->SetBounds(new_bounds);
133}
134
135void ToastContentsView::SetBoundsWithAnimation(gfx::Rect new_bounds) {
136  if (new_bounds == bounds())
137    return;
138
139  origin_ = new_bounds.origin();
140  if (!GetWidget())
141    return;
142
143  // This picks up the current bounds, so if there was a previous animation
144  // half-done, the next one will pick up from the current location.
145  // This is the only place that should query current location of the Widget
146  // on screen, the rest should refer to the bounds_.
147  animated_bounds_start_ = GetWidget()->GetWindowBoundsInScreen();
148  animated_bounds_end_ = new_bounds;
149
150  if (collection_)
151    collection_->IncrementDeferCounter();
152
153  if (bounds_animation_.get())
154    bounds_animation_->Stop();
155
156  bounds_animation_.reset(new ui::SlideAnimation(this));
157  bounds_animation_->Show();
158}
159
160void ToastContentsView::StartFadeIn() {
161  // The decrement is done in OnBoundsAnimationEndedOrCancelled callback.
162  if (collection_)
163    collection_->IncrementDeferCounter();
164  fade_animation_->Stop();
165
166  GetWidget()->SetOpacity(0);
167  GetWidget()->Show();
168  fade_animation_->Reset(0);
169  fade_animation_->Show();
170}
171
172void ToastContentsView::StartFadeOut() {
173  // The decrement is done in OnBoundsAnimationEndedOrCancelled callback.
174  if (collection_)
175    collection_->IncrementDeferCounter();
176  fade_animation_->Stop();
177
178  closing_animation_ = (is_closing_ ? fade_animation_.get() : NULL);
179  fade_animation_->Reset(1);
180  fade_animation_->Hide();
181}
182
183void ToastContentsView::OnBoundsAnimationEndedOrCancelled(
184    const ui::Animation* animation) {
185  if (is_closing_ && closing_animation_ == animation && GetWidget()) {
186    views::Widget* widget = GetWidget();
187#if defined(USE_AURA)
188    // TODO(dewittj): This is a workaround to prevent a nasty bug where
189    // closing a transparent widget doesn't actually remove the window,
190    // causing entire areas of the screen to become unresponsive to clicks.
191    // See crbug.com/243469
192    widget->Hide();
193# if defined(OS_WIN)
194    widget->SetOpacity(0xFF);
195# endif
196#endif
197    widget->Close();
198  }
199
200  // This cannot be called before GetWidget()->Close(). Decrementing defer count
201  // will invoke update, which may invoke another close animation with
202  // incrementing defer counter. Close() after such process will cause a
203  // mismatch between increment/decrement. See crbug.com/238477
204  if (collection_)
205    collection_->DecrementDeferCounter();
206}
207
208// ui::AnimationDelegate
209void ToastContentsView::AnimationProgressed(const ui::Animation* animation) {
210  if (animation == bounds_animation_.get()) {
211    gfx::Rect current(animation->CurrentValueBetween(
212        animated_bounds_start_, animated_bounds_end_));
213    GetWidget()->SetBounds(current);
214  } else if (animation == fade_animation_.get()) {
215    unsigned char opacity =
216        static_cast<unsigned char>(fade_animation_->GetCurrentValue() * 255);
217    GetWidget()->SetOpacity(opacity);
218  }
219}
220
221void ToastContentsView::AnimationEnded(const ui::Animation* animation) {
222  OnBoundsAnimationEndedOrCancelled(animation);
223}
224
225void ToastContentsView::AnimationCanceled(
226    const ui::Animation* animation) {
227  OnBoundsAnimationEndedOrCancelled(animation);
228}
229
230// views::WidgetDelegate
231views::View* ToastContentsView::GetContentsView() {
232  return this;
233}
234
235void ToastContentsView::WindowClosing() {
236  if (!is_closing_ && collection_)
237    collection_->RemoveToast(this);
238}
239
240bool ToastContentsView::CanActivate() const {
241#if defined(OS_WIN) && defined(USE_AURA)
242  return true;
243#else
244  return false;
245#endif
246}
247
248void ToastContentsView::OnDisplayChanged() {
249  views::Widget* widget = GetWidget();
250  if (!widget)
251    return;
252
253  gfx::NativeView native_view = widget->GetNativeView();
254  if (!native_view || !collection_)
255    return;
256
257  collection_->OnDisplayBoundsChanged(gfx::Screen::GetScreenFor(
258      native_view)->GetDisplayNearestWindow(native_view));
259}
260
261void ToastContentsView::OnWorkAreaChanged() {
262  views::Widget* widget = GetWidget();
263  if (!widget)
264    return;
265
266  gfx::NativeView native_view = widget->GetNativeView();
267  if (!native_view || !collection_)
268    return;
269
270  collection_->OnDisplayBoundsChanged(gfx::Screen::GetScreenFor(
271      native_view)->GetDisplayNearestWindow(native_view));
272}
273
274// views::View
275void ToastContentsView::OnMouseEntered(const ui::MouseEvent& event) {
276  if (collection_)
277    collection_->OnMouseEntered(this);
278}
279
280void ToastContentsView::OnMouseExited(const ui::MouseEvent& event) {
281  if (collection_)
282    collection_->OnMouseExited(this);
283}
284
285void ToastContentsView::Layout() {
286  if (child_count() > 0) {
287    child_at(0)->SetBounds(
288        0, 0, preferred_size_.width(), preferred_size_.height());
289  }
290}
291
292gfx::Size ToastContentsView::GetPreferredSize() {
293  return child_count() ? GetToastSizeForView(child_at(0)) : gfx::Size();
294}
295
296void ToastContentsView::GetAccessibleState(ui::AccessibleViewState* state) {
297  if (child_count() > 0)
298    child_at(0)->GetAccessibleState(state);
299  state->role = ui::AccessibilityTypes::ROLE_WINDOW;
300}
301
302gfx::Rect ToastContentsView::GetClosedToastBounds(gfx::Rect bounds) {
303  return gfx::Rect(bounds.x() + bounds.width() - kClosedToastWidth,
304                   bounds.y(),
305                   kClosedToastWidth,
306                   bounds.height());
307}
308
309}  // namespace message_center
310