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 "base/timer/timer.h"
13 #include "ui/base/accessibility/accessible_view_state.h"
14 #include "ui/gfx/animation/animation_delegate.h"
15 #include "ui/gfx/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"
27 namespace message_center
{
30 // The width of a toast before animated reveal and after closing.
31 const int kClosedToastWidth
= 5;
33 // FadeIn/Out look a bit better if they are slightly longer then default slide.
34 const int kFadeInOutDuration
= 200;
39 gfx::Size
ToastContentsView::GetToastSizeForView(views::View
* view
) {
40 int width
= kNotificationWidth
+ view
->GetInsets().width();
41 return gfx::Size(width
, view
->GetHeightForWidth(width
));
44 ToastContentsView::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),
53 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
);
66 // This is destroyed when the toast window closes.
67 ToastContentsView::~ToastContentsView() {
70 views::Widget
* ToastContentsView::CreateWidget(gfx::NativeView parent
) {
71 views::Widget::InitParams
params(
72 views::Widget::InitParams::TYPE_POPUP
);
73 params
.keep_on_top
= true;
75 params
.parent
= parent
;
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);
86 void ToastContentsView::SetContents(MessageView
* view
) {
87 bool already_has_contents
= child_count() > 0;
88 RemoveAllChildViews(true);
90 preferred_size_
= GetToastSizeForView(view
);
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 // The notification type should be ALERT, otherwise the accessibility message
95 // won't be read for this view which returns ROLE_WINDOW.
96 if (already_has_contents
) {
97 const NotificationList::Notifications
& notifications
=
98 message_center_
->GetVisibleNotifications();
99 for (NotificationList::Notifications::const_iterator iter
=
100 notifications
.begin(); iter
!= notifications
.end(); ++iter
) {
101 if ((*iter
)->id() != id_
)
104 const RichNotificationData
& optional
= (*iter
)->rich_notification_data();
105 if (optional
.should_make_spoken_feedback_for_popup_updates
)
106 NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_ALERT
, false);
112 void ToastContentsView::RevealWithAnimation(gfx::Point origin
) {
113 // Place/move the toast widgets. Currently it stacks the widgets from the
114 // right-bottom of the work area.
115 // TODO(mukai): allow to specify the placement policy from outside of this
116 // class. The policy should be specified from preference on Windows, or
117 // the launcher alignment on ChromeOS.
118 origin_
= gfx::Point(origin
.x() - preferred_size_
.width(),
119 origin
.y() - preferred_size_
.height());
121 gfx::Rect
stable_bounds(origin_
, preferred_size_
);
123 SetBoundsInstantly(GetClosedToastBounds(stable_bounds
));
125 SetBoundsWithAnimation(stable_bounds
);
128 void ToastContentsView::CloseWithAnimation(bool mark_as_shown
) {
133 collection_
->RemoveToast(this);
135 message_center_
->MarkSinglePopupAsShown(id(), false);
139 void ToastContentsView::SetBoundsInstantly(gfx::Rect new_bounds
) {
140 if (new_bounds
== bounds())
143 origin_
= new_bounds
.origin();
146 GetWidget()->SetBounds(new_bounds
);
149 void ToastContentsView::SetBoundsWithAnimation(gfx::Rect new_bounds
) {
150 if (new_bounds
== bounds())
153 origin_
= new_bounds
.origin();
157 // This picks up the current bounds, so if there was a previous animation
158 // half-done, the next one will pick up from the current location.
159 // This is the only place that should query current location of the Widget
160 // on screen, the rest should refer to the bounds_.
161 animated_bounds_start_
= GetWidget()->GetWindowBoundsInScreen();
162 animated_bounds_end_
= new_bounds
;
165 collection_
->IncrementDeferCounter();
167 if (bounds_animation_
.get())
168 bounds_animation_
->Stop();
170 bounds_animation_
.reset(new gfx::SlideAnimation(this));
171 bounds_animation_
->Show();
174 void ToastContentsView::StartFadeIn() {
175 // The decrement is done in OnBoundsAnimationEndedOrCancelled callback.
177 collection_
->IncrementDeferCounter();
178 fade_animation_
->Stop();
180 GetWidget()->SetOpacity(0);
182 fade_animation_
->Reset(0);
183 fade_animation_
->Show();
186 void ToastContentsView::StartFadeOut() {
187 // The decrement is done in OnBoundsAnimationEndedOrCancelled callback.
189 collection_
->IncrementDeferCounter();
190 fade_animation_
->Stop();
192 closing_animation_
= (is_closing_
? fade_animation_
.get() : NULL
);
193 fade_animation_
->Reset(1);
194 fade_animation_
->Hide();
197 void ToastContentsView::OnBoundsAnimationEndedOrCancelled(
198 const gfx::Animation
* animation
) {
199 if (is_closing_
&& closing_animation_
== animation
&& GetWidget()) {
200 views::Widget
* widget
= GetWidget();
201 #if defined(USE_AURA)
202 // TODO(dewittj): This is a workaround to prevent a nasty bug where
203 // closing a transparent widget doesn't actually remove the window,
204 // causing entire areas of the screen to become unresponsive to clicks.
205 // See crbug.com/243469
208 widget
->SetOpacity(0xFF);
214 // This cannot be called before GetWidget()->Close(). Decrementing defer count
215 // will invoke update, which may invoke another close animation with
216 // incrementing defer counter. Close() after such process will cause a
217 // mismatch between increment/decrement. See crbug.com/238477
219 collection_
->DecrementDeferCounter();
222 // gfx::AnimationDelegate
223 void ToastContentsView::AnimationProgressed(const gfx::Animation
* animation
) {
224 if (animation
== bounds_animation_
.get()) {
225 gfx::Rect
current(animation
->CurrentValueBetween(
226 animated_bounds_start_
, animated_bounds_end_
));
227 GetWidget()->SetBounds(current
);
228 } else if (animation
== fade_animation_
.get()) {
229 unsigned char opacity
=
230 static_cast<unsigned char>(fade_animation_
->GetCurrentValue() * 255);
231 GetWidget()->SetOpacity(opacity
);
235 void ToastContentsView::AnimationEnded(const gfx::Animation
* animation
) {
236 OnBoundsAnimationEndedOrCancelled(animation
);
239 void ToastContentsView::AnimationCanceled(
240 const gfx::Animation
* animation
) {
241 OnBoundsAnimationEndedOrCancelled(animation
);
244 // views::WidgetDelegate
245 views::View
* ToastContentsView::GetContentsView() {
249 void ToastContentsView::WindowClosing() {
250 if (!is_closing_
&& collection_
)
251 collection_
->RemoveToast(this);
254 bool ToastContentsView::CanActivate() const {
255 #if defined(OS_WIN) && defined(USE_AURA)
262 void ToastContentsView::OnDisplayChanged() {
263 views::Widget
* widget
= GetWidget();
267 gfx::NativeView native_view
= widget
->GetNativeView();
268 if (!native_view
|| !collection_
)
271 collection_
->OnDisplayBoundsChanged(gfx::Screen::GetScreenFor(
272 native_view
)->GetDisplayNearestWindow(native_view
));
275 void ToastContentsView::OnWorkAreaChanged() {
276 views::Widget
* widget
= GetWidget();
280 gfx::NativeView native_view
= widget
->GetNativeView();
281 if (!native_view
|| !collection_
)
284 collection_
->OnDisplayBoundsChanged(gfx::Screen::GetScreenFor(
285 native_view
)->GetDisplayNearestWindow(native_view
));
289 void ToastContentsView::OnMouseEntered(const ui::MouseEvent
& event
) {
291 collection_
->OnMouseEntered(this);
294 void ToastContentsView::OnMouseExited(const ui::MouseEvent
& event
) {
296 collection_
->OnMouseExited(this);
299 void ToastContentsView::Layout() {
300 if (child_count() > 0) {
301 child_at(0)->SetBounds(
302 0, 0, preferred_size_
.width(), preferred_size_
.height());
306 gfx::Size
ToastContentsView::GetPreferredSize() {
307 return child_count() ? GetToastSizeForView(child_at(0)) : gfx::Size();
310 void ToastContentsView::GetAccessibleState(ui::AccessibleViewState
* state
) {
311 if (child_count() > 0)
312 child_at(0)->GetAccessibleState(state
);
313 state
->role
= ui::AccessibilityTypes::ROLE_WINDOW
;
316 gfx::Rect
ToastContentsView::GetClosedToastBounds(gfx::Rect bounds
) {
317 return gfx::Rect(bounds
.x() + bounds
.width() - kClosedToastWidth
,
323 } // namespace message_center