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.
5 #include "ui/message_center/views/toast_contents_view.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 "ui/accessibility/ax_view_state.h"
13 #include "ui/gfx/animation/animation_delegate.h"
14 #include "ui/gfx/animation/slide_animation.h"
15 #include "ui/gfx/display.h"
16 #include "ui/gfx/screen.h"
17 #include "ui/message_center/message_center_style.h"
18 #include "ui/message_center/notification.h"
19 #include "ui/message_center/views/message_popup_collection.h"
20 #include "ui/message_center/views/message_view.h"
21 #include "ui/views/background.h"
22 #include "ui/views/view.h"
23 #include "ui/views/widget/widget.h"
24 #include "ui/views/widget/widget_delegate.h"
27 #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
32 namespace message_center
{
35 // The width of a toast before animated reveal and after closing.
36 const int kClosedToastWidth
= 5;
38 // FadeIn/Out look a bit better if they are slightly longer then default slide.
39 const int kFadeInOutDuration
= 200;
44 gfx::Size
ToastContentsView::GetToastSizeForView(const views::View
* view
) {
45 int width
= kNotificationWidth
+ view
->GetInsets().width();
46 return gfx::Size(width
, view
->GetHeightForWidth(width
));
49 ToastContentsView::ToastContentsView(
50 const std::string
& notification_id
,
51 base::WeakPtr
<MessagePopupCollection
> collection
)
52 : collection_(collection
),
55 closing_animation_(NULL
) {
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));
62 fade_animation_
.reset(new gfx::SlideAnimation(this));
63 fade_animation_
->SetSlideDuration(kFadeInOutDuration
);
65 CreateWidget(collection
->parent());
68 // This is destroyed when the toast window closes.
69 ToastContentsView::~ToastContentsView() {
71 collection_
->ForgetToast(this);
74 void ToastContentsView::SetContents(MessageView
* view
,
75 bool a11y_feedback_for_updates
) {
76 bool already_has_contents
= child_count() > 0;
77 RemoveAllChildViews(true);
79 preferred_size_
= GetToastSizeForView(view
);
82 // If it has the contents already, this invocation means an update of the
83 // popup toast, and the new contents should be read through a11y feature.
84 // The notification type should be ALERT, otherwise the accessibility message
85 // won't be read for this view which returns ROLE_WINDOW.
86 if (already_has_contents
&& a11y_feedback_for_updates
)
87 NotifyAccessibilityEvent(ui::AX_EVENT_ALERT
, false);
90 void ToastContentsView::UpdateContents(const Notification
& notification
,
91 bool a11y_feedback_for_updates
) {
92 DCHECK_GT(child_count(), 0);
93 MessageView
* message_view
= static_cast<MessageView
*>(child_at(0));
94 message_view
->UpdateWithNotification(notification
);
95 gfx::Size new_size
= GetToastSizeForView(message_view
);
96 if (preferred_size_
!= new_size
) {
97 preferred_size_
= new_size
;
100 if (a11y_feedback_for_updates
)
101 NotifyAccessibilityEvent(ui::AX_EVENT_ALERT
, false);
104 void ToastContentsView::RevealWithAnimation(gfx::Point origin
) {
105 // Place/move the toast widgets. Currently it stacks the widgets from the
106 // right-bottom of the work area.
107 // TODO(mukai): allow to specify the placement policy from outside of this
108 // class. The policy should be specified from preference on Windows, or
109 // the launcher alignment on ChromeOS.
110 origin_
= gfx::Point(origin
.x() - preferred_size_
.width(),
111 origin
.y() - preferred_size_
.height());
113 gfx::Rect
stable_bounds(origin_
, preferred_size_
);
115 SetBoundsInstantly(GetClosedToastBounds(stable_bounds
));
117 SetBoundsWithAnimation(stable_bounds
);
120 void ToastContentsView::CloseWithAnimation() {
127 void ToastContentsView::SetBoundsInstantly(gfx::Rect new_bounds
) {
128 if (new_bounds
== bounds())
131 origin_
= new_bounds
.origin();
134 GetWidget()->SetBounds(new_bounds
);
137 void ToastContentsView::SetBoundsWithAnimation(gfx::Rect new_bounds
) {
138 if (new_bounds
== bounds())
141 origin_
= new_bounds
.origin();
145 // This picks up the current bounds, so if there was a previous animation
146 // half-done, the next one will pick up from the current location.
147 // This is the only place that should query current location of the Widget
148 // on screen, the rest should refer to the bounds_.
149 animated_bounds_start_
= GetWidget()->GetWindowBoundsInScreen();
150 animated_bounds_end_
= new_bounds
;
153 collection_
->IncrementDeferCounter();
155 if (bounds_animation_
.get())
156 bounds_animation_
->Stop();
158 bounds_animation_
.reset(new gfx::SlideAnimation(this));
159 bounds_animation_
->Show();
162 void ToastContentsView::StartFadeIn() {
163 // The decrement is done in OnBoundsAnimationEndedOrCancelled callback.
165 collection_
->IncrementDeferCounter();
166 fade_animation_
->Stop();
168 GetWidget()->SetOpacity(0);
169 GetWidget()->ShowInactive();
170 fade_animation_
->Reset(0);
171 fade_animation_
->Show();
174 void ToastContentsView::StartFadeOut() {
175 // The decrement is done in OnBoundsAnimationEndedOrCancelled callback.
177 collection_
->IncrementDeferCounter();
178 fade_animation_
->Stop();
180 closing_animation_
= (is_closing_
? fade_animation_
.get() : NULL
);
181 fade_animation_
->Reset(1);
182 fade_animation_
->Hide();
185 void ToastContentsView::OnBoundsAnimationEndedOrCancelled(
186 const gfx::Animation
* animation
) {
187 if (is_closing_
&& closing_animation_
== animation
&& GetWidget()) {
188 views::Widget
* widget
= GetWidget();
190 // TODO(dewittj): This is a workaround to prevent a nasty bug where
191 // closing a transparent widget doesn't actually remove the window,
192 // causing entire areas of the screen to become unresponsive to clicks.
193 // See crbug.com/243469
196 widget
->SetOpacity(0xFF);
202 // This cannot be called before GetWidget()->Close(). Decrementing defer count
203 // will invoke update, which may invoke another close animation with
204 // incrementing defer counter. Close() after such process will cause a
205 // mismatch between increment/decrement. See crbug.com/238477
207 collection_
->DecrementDeferCounter();
210 // gfx::AnimationDelegate
211 void ToastContentsView::AnimationProgressed(const gfx::Animation
* animation
) {
212 if (animation
== bounds_animation_
.get()) {
213 gfx::Rect
current(animation
->CurrentValueBetween(
214 animated_bounds_start_
, animated_bounds_end_
));
215 GetWidget()->SetBounds(current
);
216 } else if (animation
== fade_animation_
.get()) {
217 unsigned char opacity
=
218 static_cast<unsigned char>(fade_animation_
->GetCurrentValue() * 255);
219 GetWidget()->SetOpacity(opacity
);
223 void ToastContentsView::AnimationEnded(const gfx::Animation
* animation
) {
224 OnBoundsAnimationEndedOrCancelled(animation
);
227 void ToastContentsView::AnimationCanceled(
228 const gfx::Animation
* animation
) {
229 OnBoundsAnimationEndedOrCancelled(animation
);
232 // views::WidgetDelegate
233 views::View
* ToastContentsView::GetContentsView() {
237 void ToastContentsView::WindowClosing() {
238 if (!is_closing_
&& collection_
.get())
239 collection_
->ForgetToast(this);
242 void ToastContentsView::OnDisplayChanged() {
243 views::Widget
* widget
= GetWidget();
247 gfx::NativeView native_view
= widget
->GetNativeView();
248 if (!native_view
|| !collection_
.get())
251 collection_
->OnDisplayMetricsChanged(
252 Screen::GetScreenFor(native_view
)->GetDisplayNearestWindow(native_view
));
255 void ToastContentsView::OnWorkAreaChanged() {
256 views::Widget
* widget
= GetWidget();
260 gfx::NativeView native_view
= widget
->GetNativeView();
261 if (!native_view
|| !collection_
.get())
264 collection_
->OnDisplayMetricsChanged(
265 Screen::GetScreenFor(native_view
)->GetDisplayNearestWindow(native_view
));
269 void ToastContentsView::OnMouseEntered(const ui::MouseEvent
& event
) {
271 collection_
->OnMouseEntered(this);
274 void ToastContentsView::OnMouseExited(const ui::MouseEvent
& event
) {
276 collection_
->OnMouseExited(this);
279 void ToastContentsView::Layout() {
280 if (child_count() > 0) {
281 child_at(0)->SetBounds(
282 0, 0, preferred_size_
.width(), preferred_size_
.height());
286 gfx::Size
ToastContentsView::GetPreferredSize() const {
287 return child_count() ? GetToastSizeForView(child_at(0)) : gfx::Size();
290 void ToastContentsView::GetAccessibleState(ui::AXViewState
* state
) {
291 if (child_count() > 0)
292 child_at(0)->GetAccessibleState(state
);
293 state
->role
= ui::AX_ROLE_WINDOW
;
296 void ToastContentsView::ClickOnNotification(
297 const std::string
& notification_id
) {
299 collection_
->ClickOnNotification(notification_id
);
302 void ToastContentsView::RemoveNotification(
303 const std::string
& notification_id
,
306 collection_
->RemoveNotification(notification_id
, by_user
);
309 scoped_ptr
<ui::MenuModel
> ToastContentsView::CreateMenuModel(
310 const NotifierId
& notifier_id
,
311 const base::string16
& display_source
) {
312 // Should not reach, the context menu should be handled in
313 // MessagePopupCollection.
318 bool ToastContentsView::HasClickedListener(
319 const std::string
& notification_id
) {
322 return collection_
->HasClickedListener(notification_id
);
325 void ToastContentsView::ClickOnNotificationButton(
326 const std::string
& notification_id
,
329 collection_
->ClickOnNotificationButton(notification_id
, button_index
);
332 void ToastContentsView::CreateWidget(gfx::NativeView parent
) {
333 views::Widget::InitParams
params(views::Widget::InitParams::TYPE_POPUP
);
334 params
.keep_on_top
= true;
336 params
.parent
= parent
;
337 params
.opacity
= views::Widget::InitParams::TRANSLUCENT_WINDOW
;
338 params
.delegate
= this;
339 views::Widget
* widget
= new views::Widget();
340 widget
->set_focus_on_creation(false);
343 // We want to ensure that this toast always goes to the native desktop,
344 // not the Ash desktop (since there is already another toast contents view
347 params
.native_widget
= new views::DesktopNativeWidgetAura(widget
);
350 widget
->Init(params
);
353 gfx::Rect
ToastContentsView::GetClosedToastBounds(gfx::Rect bounds
) {
354 return gfx::Rect(bounds
.x() + bounds
.width() - kClosedToastWidth
,
360 } // namespace message_center